11
11
import importlib .util
12
12
import logging
13
13
import os
14
+ import json
14
15
import re
15
16
import shutil
16
17
import subprocess
17
18
import sys
19
+ import platform
18
20
import textwrap
19
21
import shlex
20
22
21
23
from pathlib import Path
22
24
from collections import OrderedDict
23
25
from typing import Dict
26
+ from typing import Set
24
27
from typing import List
25
28
from typing import Mapping
26
29
from typing import Optional
27
30
from typing import Type
28
31
from typing import Union
29
32
30
- from cloe_launch .utility import run_cmd
31
33
from cloe_launch import Configuration
34
+ import cloe_launch .utility as cloe_utils
35
+ from cloe_launch .utility import run_cmd
32
36
33
37
34
38
class Environment :
@@ -403,17 +407,18 @@ def runtime_env_path(self) -> Path:
403
407
"""Return the path to the list of pruned environment variables."""
404
408
return self .runtime_dir / "environment_all.sh.env"
405
409
406
- def _prepare_runtime_dir (self ) -> None :
410
+ def _prepare_runtime_dir (self , with_json : bool = False ) -> None :
407
411
"""Clean and create runtime directory."""
408
412
self .clean ()
409
413
logging .debug (f"Create: { self .runtime_dir } " )
410
414
self .runtime_dir .mkdir (parents = True )
411
- self ._prepare_virtualenv ()
415
+ self ._prepare_virtualenv (with_json )
412
416
self ._write_cloe_env ()
413
417
self ._write_activate_all (
414
418
[
415
419
# From Conan VirtualRunEnv (!= virtualrunenv) generator:
416
- self .runtime_dir / "conanrun.sh" ,
420
+ self .runtime_dir
421
+ / "conanrun.sh" ,
417
422
],
418
423
[
419
424
# From Conan virtualenv generator:
@@ -424,7 +429,7 @@ def _prepare_runtime_dir(self) -> None:
424
429
self .runtime_dir / "environment_cloe_launch.sh.env" ,
425
430
# From self._write_cloe_env(), derived from environment_run.sh:
426
431
self .runtime_dir / "environment_cloe.sh.env" ,
427
- ]
432
+ ],
428
433
)
429
434
self ._write_prompt_sh ()
430
435
self ._write_bashrc ()
@@ -496,21 +501,35 @@ def _write_zshrc(self) -> None:
496
501
497
502
def _write_cloe_env (self ) -> None :
498
503
"""Derive important CLOE_ variables and write environment_cloe.sh.env file."""
499
- conanrun = self .runtime_dir / "conanrun.sh" # From newer VirtualRunEnv generator
500
- activate_run = self .runtime_dir / "activate_run.sh" # From older virtualrunenv generator
504
+ conanrun = (
505
+ self .runtime_dir / "conanrun.sh"
506
+ ) # From newer VirtualRunEnv generator
507
+ activate_run = (
508
+ self .runtime_dir / "activate_run.sh"
509
+ ) # From older virtualrunenv generator
501
510
if conanrun .exists ():
502
511
if activate_run .exists ():
503
- logging .warning ("Warning: Found both conanrun.sh and activate_run.sh in runtime directory!" )
512
+ logging .warning (
513
+ "Warning: Found both conanrun.sh and activate_run.sh in runtime directory!"
514
+ )
504
515
logging .warning ("Note:" )
505
- logging .warning (" It looks like /both/ VirtualRunEnv and virtualrunenv generators are being run." )
506
- logging .warning (" This may come from using an out-of-date cloe-launch-profile package." )
516
+ logging .warning (
517
+ " It looks like /both/ VirtualRunEnv and virtualrunenv generators are being run."
518
+ )
519
+ logging .warning (
520
+ " This may come from using an out-of-date cloe-launch-profile package."
521
+ )
507
522
logging .warning ("" )
508
- logging .warning (" Continuing with hybrid approach. Environment variables may be incorrectly set." )
509
- env = Environment (conanrun , source_file = True )
523
+ logging .warning (
524
+ " Continuing with hybrid approach. Environment variables may be incorrectly set."
525
+ )
526
+ env = Environment (conanrun , source_file = True )
510
527
elif activate_run .exists ():
511
- env = Environment (activate_run , source_file = True )
528
+ env = Environment (activate_run , source_file = True )
512
529
else :
513
- raise RuntimeError ("cannot find conanrun.sh or activate_run.sh in runtime directory" )
530
+ raise RuntimeError (
531
+ "cannot find conanrun.sh or activate_run.sh in runtime directory"
532
+ )
514
533
515
534
if env .has ("CLOE_SHELL" ):
516
535
logging .error ("Error: recursive cloe shells are not supported." )
@@ -531,7 +550,9 @@ def _write_cloe_env(self) -> None:
531
550
cloe_env .path_set ("CLOE_PLUGIN_PATH" , self ._extract_plugin_paths (env ))
532
551
cloe_env .export (self .runtime_dir / "environment_cloe.sh.env" )
533
552
534
- def _write_activate_all (self , source_files : List [Path ], env_files : List [Path ]) -> None :
553
+ def _write_activate_all (
554
+ self , source_files : List [Path ], env_files : List [Path ]
555
+ ) -> None :
535
556
"""Write activate_all.sh file."""
536
557
activate_file = self .runtime_dir / "activate_all.sh"
537
558
activate_data = textwrap .dedent (
@@ -566,7 +587,7 @@ def _write_activate_all(self, source_files: List[Path], env_files: List[Path]) -
566
587
with activate_file .open ("w" ) as file :
567
588
file .write (activate_data )
568
589
569
- def _prepare_virtualenv (self ) -> None :
590
+ def _prepare_virtualenv (self , with_json : bool = False ) -> None :
570
591
# Get conan to create a virtualenv AND virtualrunenv for us:
571
592
# One gives us the LD_LIBRARY_PATH and the other gives us env_info
572
593
# variables set in packages.
@@ -578,6 +599,9 @@ def _prepare_virtualenv(self) -> None:
578
599
"-g" ,
579
600
"VirtualRunEnv" ,
580
601
]
602
+ if with_json :
603
+ conan_cmd .append ("-g" )
604
+ conan_cmd .append ("json" )
581
605
for arg in self .conan_args :
582
606
conan_cmd .append (arg )
583
607
for option in self .conan_options :
@@ -600,8 +624,12 @@ def _extract_engine_path(self, env: Environment) -> Path:
600
624
logging .error ("Note:" )
601
625
logging .error (" This problem usually stems from one of two common errors:" )
602
626
logging .error (" - The conanfile for cloe-launch does not require cloe-engine." )
603
- logging .error (" - The cloe-engine package or binary has not been built / is corrupted." )
604
- logging .error (" However, unconvential or unsupported package configuration may also trigger this." )
627
+ logging .error (
628
+ " - The cloe-engine package or binary has not been built / is corrupted."
629
+ )
630
+ logging .error (
631
+ " However, unconvential or unsupported package configuration may also trigger this."
632
+ )
605
633
sys .exit (2 )
606
634
607
635
def _extract_plugin_paths (self , env : Environment ) -> List [Path ]:
@@ -623,12 +651,14 @@ def _extract_plugin_setups(self, lib_paths: List[Path]) -> List[Type[PluginSetup
623
651
_find_plugin_setups (path )
624
652
return PluginSetup .__subclasses__ ()
625
653
626
- def _prepare_runtime_env (self , use_cache : bool = False ) -> Environment :
654
+ def _prepare_runtime_env (
655
+ self , use_cache : bool = False , with_json : bool = False
656
+ ) -> Environment :
627
657
if self .runtime_env_path ().exists () and use_cache :
628
658
logging .debug ("Re-using existing runtime directory." )
629
659
else :
630
660
logging .debug ("Initializing runtime directory ..." )
631
- self ._prepare_runtime_dir ()
661
+ self ._prepare_runtime_dir (with_json = with_json )
632
662
633
663
# Get environment variables we need:
634
664
return Environment (
@@ -758,6 +788,103 @@ def prepare(self, build_policy: str = "outdated") -> None:
758
788
self .conan_args .append (f"--build={ build_policy } " )
759
789
self ._prepare_runtime_env (use_cache = False )
760
790
791
+ def deploy (
792
+ self ,
793
+ dest : Path ,
794
+ wrapper : Optional [Path ] = None ,
795
+ wrapper_target : Optional [Path ] = None ,
796
+ patch_rpath : bool = True ,
797
+ build_policy : str = "outdated" ,
798
+ ) -> None :
799
+ """Deploy dependencies for the profile."""
800
+ self .capture_output = False
801
+ if build_policy == "" :
802
+ self .conan_args .append ("--build" )
803
+ else :
804
+ self .conan_args .append (f"--build={ build_policy } " )
805
+ self ._prepare_runtime_env (use_cache = False , with_json = True )
806
+
807
+ # Ensure destination exists:
808
+ if not dest .is_dir ():
809
+ if dest .exists ():
810
+ logging .error (f"Error: destination is not a directory: { dest } " )
811
+ sys .exit (1 )
812
+ dest .mkdir (parents = True )
813
+
814
+ # Copy necessary files to destination:
815
+ # TODO: Create a manifest and be verbose about files being copied.
816
+ build_info = self .runtime_dir / "conanbuildinfo.json"
817
+ logging .info (f"Reading: { build_info } " )
818
+ build_data = json .load (build_info .open ())
819
+ install_manifest : List [Path ] = []
820
+
821
+ def copy_file (src , dest ):
822
+ install_manifest .append (Path (dest ))
823
+ logging .info (f"Installing: { dest } " )
824
+ return shutil .copy2 (src , dest )
825
+
826
+ def copy_tree (src , dest , ignore ):
827
+ if src .find ("/build/" ) != - 1 :
828
+ logging .warning (
829
+ f"Warning: deploying from build directory is strongly discouraged: { dep ['rootpath' ]} "
830
+ )
831
+ shutil .copytree (
832
+ src ,
833
+ dest ,
834
+ copy_function = copy_file ,
835
+ dirs_exist_ok = True ,
836
+ ignore = shutil .ignore_patterns (* ignore ),
837
+ )
838
+
839
+ for dep in build_data ["dependencies" ]:
840
+ for src in dep ["bin_paths" ]:
841
+ copy_tree (src , dest / "bin" , ignore = ["bzip2" ])
842
+ for src in dep ["lib_paths" ]:
843
+ copy_tree (src , dest / "lib" , ignore = ["cmake" , "*.a" ])
844
+
845
+ # Patching RPATH of all the binaries lets everything run
846
+ # fine without any extra steps, like setting LD_LIBRARY_PATH.
847
+ if patch_rpath :
848
+ assert platform .system () != "Windows"
849
+ cloe_utils .patch_binary_files_rpath (dest / "bin" , ["$ORIGIN/../lib" ])
850
+ cloe_utils .patch_binary_files_rpath (dest / "lib" / "cloe" , ["$ORIGIN/.." ])
851
+
852
+ if wrapper is not None :
853
+ if wrapper_target is None :
854
+ wrapper_target = dest / "bin" / "cloe-engine"
855
+ wrapper_data = textwrap .dedent (
856
+ f"""\
857
+ #!/bin/sh
858
+
859
+ { wrapper_target } $@
860
+ """
861
+ )
862
+ with wrapper .open ("w" ) as wrapper_file :
863
+ wrapper_file .write (wrapper_data )
864
+
865
+ def simplify_manifest (manifest : Set [Path ]):
866
+ for path in list (manifest ):
867
+ parent = path .parent
868
+ while parent != parent .parent :
869
+ if parent in manifest :
870
+ manifest .remove (parent )
871
+ parent = parent .parent
872
+
873
+ # Create uninstaller from manifest
874
+ uninstaller_file = self .runtime_dir / "uninstall.sh"
875
+ logging .info (f"Write: { uninstaller_file } " )
876
+ with uninstaller_file .open ("w" ) as f :
877
+ install_dirs : Set [Path ] = set ()
878
+ f .write ("#!/bin/bash\n " )
879
+ for file in install_manifest :
880
+ install_dirs .add (file .parent )
881
+ f .write (f"echo 'Removing file: { file } '\n " )
882
+ f .write (f"rm '{ file } '\n " )
883
+ simplify_manifest (install_dirs )
884
+ for path in install_dirs :
885
+ f .write (f"echo 'Removing dir: { path } '\n " )
886
+ f .write (f"rmdir -p '{ path } '\n " )
887
+
761
888
def exec (
762
889
self ,
763
890
args : List [str ],
0 commit comments