autoproj-sync 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +112 -0
- data/Rakefile +8 -0
- data/autoproj-sync.gemspec +29 -0
- data/lib/autoproj-sync.rb +39 -0
- data/lib/autoproj/cli/main_sync.rb +189 -0
- data/lib/autoproj/sync.rb +7 -0
- data/lib/autoproj/sync/config.rb +84 -0
- data/lib/autoproj/sync/remote.rb +399 -0
- data/lib/autoproj/sync/version.rb +5 -0
- metadata +113 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: af756c3be787939a54e4d4f17cb1b8091737000678c8850e2e20741a1db0a5f2
         | 
| 4 | 
            +
              data.tar.gz: 2640991f20d18f9972ddeeeab0aa77832987515b13a38829310983b157703c89
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: 0020ac4629195c4f9a802735cd1c3a5ec6f5e3fa3167d15d1efa1602a258422c300b8903c5228cc31574b7928c14b8329db56386d824355668848200f376f1fc
         | 
| 7 | 
            +
              data.tar.gz: d0900fb01449b29bde7678b286932e72aa849f27b548e42ab930e2933d8cde636dde9893a612534ee52820875368ce346dce445bf9233c961c02d53b645ddc64
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2018 Sylvain Joyeux
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,112 @@ | |
| 1 | 
            +
            # Autoproj::Sync
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This Autoproj plugin provides a way to keep remote location(s) synchronized
         | 
| 4 | 
            +
            with a local workspace, as part of the build process. This allows for to keep
         | 
| 5 | 
            +
            a local development workflow, but run things remotely in a very flexible way.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            It is not *that* magical. It is based on the assumption that:
         | 
| 8 | 
            +
            - the local and remote hosts have equivalent environments. In practice, it means
         | 
| 9 | 
            +
              that binaries built with the local machine are compatible with the environment
         | 
| 10 | 
            +
              on the remote machine (same shared libraries or using static libraries, ...)
         | 
| 11 | 
            +
            - the remote environment is synchronized at the exact same absolute path than
         | 
| 12 | 
            +
              the local one
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## Installation
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            Run
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ```
         | 
| 19 | 
            +
            autoproj plugin install autoproj-sync
         | 
| 20 | 
            +
            ```
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Autoproj Sync works only on workspaces that use separate prefixes. If you did
         | 
| 23 | 
            +
            not enable prefixes when bootstrapping your workspace, the easiest is to
         | 
| 24 | 
            +
            re-bootstrap a new clean one, passing the `--separate-prefixes` option.
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## Usage
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            ### Preparing the remote target
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            To function, Autoproj Sync needs the remote target to match the local target, that is:
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            - allow to run binaries built locally (meaning same or compatible shared libraries)
         | 
| 33 | 
            +
            - use the same full paths
         | 
| 34 | 
            +
            - have a compatible ruby binary at the same path than the one used locally. If you
         | 
| 35 | 
            +
              for instance use rbenv, you will need to install the same Ruby version through rbenv.
         | 
| 36 | 
            +
            - if you are using a git version of Autoproj, or plugins that are not available through
         | 
| 37 | 
            +
              RubyGems, you will have to install them manually at the same path than on the local
         | 
| 38 | 
            +
              machine
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            Moreover the remote target should be accessible via SSH public key authentication.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            ### Automated Synchronisation
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Once a target is added and enabled (see below on how to do this), Autoproj will sync
         | 
| 45 | 
            +
            it automatically after an update or build operation. This works well with
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ### Sync and the VSCode/Rock integration
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            The vscode-rock extension can use sync to debug remote programs.
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            This currently only supports C++ programs. When creating your launch entry, simply
         | 
| 52 | 
            +
            add the following line to it:
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ~~~json
         | 
| 55 | 
            +
            "miDebuggerServerAddress": "rock:<remote_name>:<remote_port>"
         | 
| 56 | 
            +
            ~~~
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            where `<remote_name>` is the name of the Sync remote and `<remote_port>` the
         | 
| 59 | 
            +
            port that should be used by `gdbserver`. Run, and that's it.
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            ### CLI Usage
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            Add and enable a synchronisation target with
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            ```
         | 
| 66 | 
            +
            autoproj sync add NAME URL
         | 
| 67 | 
            +
            ```
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            This will trigger a synchronization.
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            Targets can be listed with
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            ```
         | 
| 74 | 
            +
            autoproj sync list
         | 
| 75 | 
            +
            ```
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            And removed with
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            ```
         | 
| 80 | 
            +
            autoproj sync remove NAME
         | 
| 81 | 
            +
            ```
         | 
| 82 | 
            +
             | 
| 83 | 
            +
            Synchronisation can be temporarily disabled with
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            ```
         | 
| 86 | 
            +
            autoproj sync disable
         | 
| 87 | 
            +
            ```
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            And reenabled with
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            ```
         | 
| 92 | 
            +
            autoproj sync enable
         | 
| 93 | 
            +
            ```
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            Re-enabling a target will force a synchronisation to occur. Once a target is
         | 
| 96 | 
            +
            enabled, synchronisation happens during the build, whenever a package has
         | 
| 97 | 
            +
            been built and installed.
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            The `enable` and `disable` subcommands both accept target names, which allows
         | 
| 100 | 
            +
            to selectively enable and disable.
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            ```
         | 
| 103 | 
            +
            autoproj sync enable
         | 
| 104 | 
            +
            ```
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ## Contributing
         | 
| 107 | 
            +
             | 
| 108 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/rock-core/autoproj-sync.
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            ## License
         | 
| 111 | 
            +
             | 
| 112 | 
            +
            The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
         | 
    
        data/Rakefile
    ADDED
    
    
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            lib = File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require "autoproj/sync/version"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
                spec.name          = "autoproj-sync"
         | 
| 8 | 
            +
                spec.version       = Autoproj::Sync::VERSION
         | 
| 9 | 
            +
                spec.authors       = ["Sylvain Joyeux"]
         | 
| 10 | 
            +
                spec.email         = ["sylvain.joyeux@13robotics.com"]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                spec.summary       = %q{Synchronizes the build byproducts of an Autoproj workspace to remote target(s)}
         | 
| 13 | 
            +
                spec.homepage      = "https://rock-core.github.io/rock-and-syskit"
         | 
| 14 | 
            +
                spec.license       = "MIT"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Specify which files should be added to the gem when it is released.
         | 
| 17 | 
            +
                # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
         | 
| 18 | 
            +
                spec.files         = Dir.chdir(File.expand_path('..', __FILE__)) do
         | 
| 19 | 
            +
                  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                spec.bindir        = "exe"
         | 
| 22 | 
            +
                spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 23 | 
            +
                spec.require_paths = ["lib"]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                spec.add_dependency "autoproj", "~> 2.4"
         | 
| 26 | 
            +
                spec.add_dependency "net-sftp"
         | 
| 27 | 
            +
                spec.add_development_dependency "bundler", "~> 1.16"
         | 
| 28 | 
            +
                spec.add_development_dependency "minitest", "~> 5.0"
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            require 'autoproj/cli/main_sync'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class Autoproj::CLI::Main
         | 
| 4 | 
            +
                desc 'sync', 'synchronize a workspace with remote location(s)'
         | 
| 5 | 
            +
                subcommand 'sync', Autoproj::CLI::MainSync
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                register_post_command_hook(:build) do |ws, args|
         | 
| 8 | 
            +
                    source_packages = args[:source_packages]
         | 
| 9 | 
            +
                    source_packages = source_packages.map do |package_name|
         | 
| 10 | 
            +
                        ws.manifest.find_package_definition(package_name)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    config = Autoproj::Sync::Config.new(ws)
         | 
| 14 | 
            +
                    config.each_enabled_remote.each do |remote|
         | 
| 15 | 
            +
                        remote.start do |sftp|
         | 
| 16 | 
            +
                            remote.update(sftp, ws, source_packages)
         | 
| 17 | 
            +
                        end
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                register_post_command_hook(:update) do |ws, args|
         | 
| 21 | 
            +
                    source_packages, osdep_packages = args.
         | 
| 22 | 
            +
                        values_at(:source_packages, :osdep_packages)
         | 
| 23 | 
            +
                    source_packages = source_packages.map do |package_name|
         | 
| 24 | 
            +
                        ws.manifest.find_package_definition(package_name)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    config = Autoproj::Sync::Config.new(ws)
         | 
| 28 | 
            +
                    config.each_enabled_remote.each do |remote|
         | 
| 29 | 
            +
                        remote.start do |sftp|
         | 
| 30 | 
            +
                            unless source_packages.empty?
         | 
| 31 | 
            +
                                remote.update(sftp, ws, source_packages)
         | 
| 32 | 
            +
                            end
         | 
| 33 | 
            +
                            unless osdep_packages.empty?
         | 
| 34 | 
            +
                                remote.osdeps(sftp, ws, osdep_packages)
         | 
| 35 | 
            +
                            end
         | 
| 36 | 
            +
                        end
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,189 @@ | |
| 1 | 
            +
            require 'autoproj/sync'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Autoproj
         | 
| 4 | 
            +
                module CLI
         | 
| 5 | 
            +
                    # The 'jenkins' subcommand for autoproj
         | 
| 6 | 
            +
                    class MainSync < Thor
         | 
| 7 | 
            +
                        namespace 'sync'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                        no_commands do
         | 
| 10 | 
            +
                            def ws
         | 
| 11 | 
            +
                                unless @ws
         | 
| 12 | 
            +
                                    @ws = Autoproj::Workspace.default
         | 
| 13 | 
            +
                                    @ws.load_config
         | 
| 14 | 
            +
                                    unless @ws.config.separate_prefixes?
         | 
| 15 | 
            +
                                        raise RuntimeError, "autoproj-sync only works on workspaces "\
         | 
| 16 | 
            +
                                            "that have separate prefixes enabled"
         | 
| 17 | 
            +
                                    end
         | 
| 18 | 
            +
                                end
         | 
| 19 | 
            +
                                @ws
         | 
| 20 | 
            +
                            end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                            def config
         | 
| 23 | 
            +
                                unless @config
         | 
| 24 | 
            +
                                    @config = Sync::Config.new(ws)
         | 
| 25 | 
            +
                                end
         | 
| 26 | 
            +
                                @config
         | 
| 27 | 
            +
                            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                            def ws_load
         | 
| 30 | 
            +
                                ws.setup
         | 
| 31 | 
            +
                                ws.load_package_sets
         | 
| 32 | 
            +
                                ws.setup_all_package_directories
         | 
| 33 | 
            +
                                ws.finalize_package_setup
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                                source_packages, osdep_packages, _resolved_selection =
         | 
| 36 | 
            +
                                    ws.load_packages(ws.manifest.default_packages(false),
         | 
| 37 | 
            +
                                        recursive: true,
         | 
| 38 | 
            +
                                        non_imported_packages: :ignore,
         | 
| 39 | 
            +
                                        auto_exclude: false)
         | 
| 40 | 
            +
                                ws.finalize_setup
         | 
| 41 | 
            +
                                source_packages = source_packages.map do |name|
         | 
| 42 | 
            +
                                    ws.manifest.find_package_definition(name)
         | 
| 43 | 
            +
                                end
         | 
| 44 | 
            +
                                [source_packages, osdep_packages]
         | 
| 45 | 
            +
                            end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                            def resolve_selected_remotes(*names)
         | 
| 48 | 
            +
                                if names.empty?
         | 
| 49 | 
            +
                                    config.each_enabled_remote
         | 
| 50 | 
            +
                                else
         | 
| 51 | 
            +
                                    names.map { |n| config.remote_by_name(n) }
         | 
| 52 | 
            +
                                end
         | 
| 53 | 
            +
                            end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                        end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                        desc 'add NAME URL', "add a new remote target"
         | 
| 58 | 
            +
                        def add(name, uri)
         | 
| 59 | 
            +
                            if remote = config.find_remote_by_name(name)
         | 
| 60 | 
            +
                                STDERR.puts "There is already a target called #{name} pointing to "\
         | 
| 61 | 
            +
                                    "#{remote.uri}"
         | 
| 62 | 
            +
                                exit 1
         | 
| 63 | 
            +
                            end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                            remote = Sync::Remote.from_uri(URI.parse(uri), name: name)
         | 
| 66 | 
            +
                            config.add_remote(remote)
         | 
| 67 | 
            +
                            packages, = Autoproj.silent { ws_load }
         | 
| 68 | 
            +
                            remote.start do |sftp|
         | 
| 69 | 
            +
                                remote.update(sftp, ws, packages)
         | 
| 70 | 
            +
                            end
         | 
| 71 | 
            +
                        end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                        desc 'remove NAME', "remove a remote target"
         | 
| 74 | 
            +
                        def remove(name)
         | 
| 75 | 
            +
                            config.delete_remote(name)
         | 
| 76 | 
            +
                        end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        desc 'list', "lists registered targets"
         | 
| 79 | 
            +
                        def list
         | 
| 80 | 
            +
                            config.each_remote do |remote|
         | 
| 81 | 
            +
                                enabled = remote.enabled? ? 'enabled' : 'disabled'
         | 
| 82 | 
            +
                                puts "#{remote.name}: #{remote.uri} (#{enabled})"
         | 
| 83 | 
            +
                            end
         | 
| 84 | 
            +
                        end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                        desc 'status [NAME]', "accesses a target (or all enabled targets) and "\
         | 
| 87 | 
            +
                            "display outdated packages"
         | 
| 88 | 
            +
                        def status(*names)
         | 
| 89 | 
            +
                            remotes = resolve_selected_remotes(*names)
         | 
| 90 | 
            +
                            packages, = Autoproj.silent { ws_load }
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                            remotes.each do |r|
         | 
| 93 | 
            +
                                outdated_packages =
         | 
| 94 | 
            +
                                    r.start do |sftp|
         | 
| 95 | 
            +
                                        r.each_outdated_package(sftp, @ws, packages).to_a
         | 
| 96 | 
            +
                                    end
         | 
| 97 | 
            +
                                puts "#{outdated_packages.size} outdated packages"
         | 
| 98 | 
            +
                                outdated_packages.each do |pkg|
         | 
| 99 | 
            +
                                    puts "  #{pkg.name}"
         | 
| 100 | 
            +
                                end
         | 
| 101 | 
            +
                            end
         | 
| 102 | 
            +
                        end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                        desc 'update NAME', "trigger an update for a remote"
         | 
| 105 | 
            +
                        def update(*names)
         | 
| 106 | 
            +
                            remotes = resolve_selected_remotes(*names)
         | 
| 107 | 
            +
                            packages, = Autoproj.silent { ws_load }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                            remotes.each do |r|
         | 
| 110 | 
            +
                                r.start do |sftp|
         | 
| 111 | 
            +
                                    r.update(sftp, ws, packages)
         | 
| 112 | 
            +
                                end
         | 
| 113 | 
            +
                            end
         | 
| 114 | 
            +
                        end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                        desc 'enable NAME', "enables a previously disabled target, or all targets"
         | 
| 117 | 
            +
                        def enable(*names)
         | 
| 118 | 
            +
                            names = ws.config.get('sync', Hash.new).keys if names.empty?
         | 
| 119 | 
            +
                            packages = nil
         | 
| 120 | 
            +
                            names.each do |name|
         | 
| 121 | 
            +
                                config.update_remote_config(name) do |remote_config|
         | 
| 122 | 
            +
                                    unless remote_config['enabled']
         | 
| 123 | 
            +
                                        remote = config.remote_by_name(name)
         | 
| 124 | 
            +
                                        packages, = Autoproj.silent { ws_load } unless packages
         | 
| 125 | 
            +
                                        remote.start do |sftp|
         | 
| 126 | 
            +
                                            remote.update(sftp, ws, packages)
         | 
| 127 | 
            +
                                        end
         | 
| 128 | 
            +
                                        remote_config['enabled'] = true
         | 
| 129 | 
            +
                                    end
         | 
| 130 | 
            +
                                end
         | 
| 131 | 
            +
                            end
         | 
| 132 | 
            +
                        end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                        desc 'disable [NAME]', "disables a previously enabled target, or all targets"
         | 
| 135 | 
            +
                        def disable(*names)
         | 
| 136 | 
            +
                            names = ws.config.get('sync', Hash.new).keys if names.empty?
         | 
| 137 | 
            +
                            names.each do |name|
         | 
| 138 | 
            +
                                config.update_remote_config(name) do |config|
         | 
| 139 | 
            +
                                    config['enabled'] = false
         | 
| 140 | 
            +
                                end
         | 
| 141 | 
            +
                            end
         | 
| 142 | 
            +
                        end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                        desc 'install-osdeps MANAGER_TYPE <PACKAGES...>',
         | 
| 145 | 
            +
                            'install osdeps coming from the local machine',
         | 
| 146 | 
            +
                            hide: true
         | 
| 147 | 
            +
                        def install_osdeps(manager_type, *packages)
         | 
| 148 | 
            +
                            Autobuild.silent { ws_load }
         | 
| 149 | 
            +
                            installer = ws.os_package_installer
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                            installer.setup_package_managers
         | 
| 152 | 
            +
                            manager = installer.package_managers.fetch(manager_type)
         | 
| 153 | 
            +
                            installer.install_manager_packages(manager, packages)
         | 
| 154 | 
            +
                        end
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                        desc 'osdeps [NAME]', 'install the osdeps on the remote'
         | 
| 157 | 
            +
                        def osdeps(*names)
         | 
| 158 | 
            +
                            _, osdep_packages = Autobuild.silent { ws_load }
         | 
| 159 | 
            +
                            remotes = resolve_selected_remotes(*names)
         | 
| 160 | 
            +
                            remotes.each do |r|
         | 
| 161 | 
            +
                                r.start do |sftp|
         | 
| 162 | 
            +
                                    r.osdeps(sftp, ws, osdep_packages)
         | 
| 163 | 
            +
                                end
         | 
| 164 | 
            +
                            end
         | 
| 165 | 
            +
                        end
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                        desc 'exec NAME COMMAND', 'execute a command on a remote workspace'
         | 
| 168 | 
            +
                        option :interactive, doc: 'execute the command in interactive mode',
         | 
| 169 | 
            +
                            type: :boolean, default: false
         | 
| 170 | 
            +
                        option :chdir, doc: 'working directory for the command',
         | 
| 171 | 
            +
                            type: :string, default: nil
         | 
| 172 | 
            +
                        def exec(remote_name, *command)
         | 
| 173 | 
            +
                            remote = config.remote_by_name(remote_name)
         | 
| 174 | 
            +
                            status = remote.start do |sftp|
         | 
| 175 | 
            +
                                remote.remote_autoproj(sftp, ws.root_dir, "exec", *command,
         | 
| 176 | 
            +
                                    interactive: options[:interactive],
         | 
| 177 | 
            +
                                    chdir: options[:chdir] || ws.root_dir)
         | 
| 178 | 
            +
                            end
         | 
| 179 | 
            +
                            unless options[:interactive]
         | 
| 180 | 
            +
                                if status[:exit_signal]
         | 
| 181 | 
            +
                                    exit 255
         | 
| 182 | 
            +
                                else
         | 
| 183 | 
            +
                                    exit status[:exit_code]
         | 
| 184 | 
            +
                                end
         | 
| 185 | 
            +
                            end
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
                    end
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
            end
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            module Autoproj
         | 
| 2 | 
            +
                module Sync
         | 
| 3 | 
            +
                    class Config
         | 
| 4 | 
            +
                        def initialize(ws)
         | 
| 5 | 
            +
                            @ws = ws
         | 
| 6 | 
            +
                        end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                        def each_remote
         | 
| 9 | 
            +
                            return enum_for(__method__) unless block_given?
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                            targets = @ws.config.get('sync', Hash.new)
         | 
| 12 | 
            +
                            targets.each do |name, config|
         | 
| 13 | 
            +
                                yield Remote.from_uri(URI.parse(config['uri']),
         | 
| 14 | 
            +
                                    name: name, enabled: config['enabled'])
         | 
| 15 | 
            +
                            end
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        def each_enabled_remote
         | 
| 19 | 
            +
                            return enum_for(__method__) unless block_given?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                            each_remote do |remote|
         | 
| 22 | 
            +
                                yield(remote) if remote.enabled?
         | 
| 23 | 
            +
                            end
         | 
| 24 | 
            +
                        end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        def remote_by_name(name)
         | 
| 27 | 
            +
                            unless remote = find_remote_by_name(name)
         | 
| 28 | 
            +
                                raise ArgumentError, "no remote named '#{name}', "\
         | 
| 29 | 
            +
                                    "existing remotes: #{each_remote.map(&:name).sort.join(", ")}"
         | 
| 30 | 
            +
                            end
         | 
| 31 | 
            +
                            remote
         | 
| 32 | 
            +
                        end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                        def find_remote_by_name(name)
         | 
| 35 | 
            +
                            targets = @ws.config.get('sync', Hash.new)
         | 
| 36 | 
            +
                            if config = targets[name]
         | 
| 37 | 
            +
                                remote_from_config(name, config)
         | 
| 38 | 
            +
                            end
         | 
| 39 | 
            +
                        end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                        private def remote_from_config(name, config)
         | 
| 42 | 
            +
                            Remote.from_uri(URI.parse(config['uri']),
         | 
| 43 | 
            +
                                name: name, enabled: config['enabled'])
         | 
| 44 | 
            +
                        end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                        private def remote_to_config(remote)
         | 
| 47 | 
            +
                            [remote.name, Hash[
         | 
| 48 | 
            +
                                'uri' => remote.uri.to_s,
         | 
| 49 | 
            +
                                'enabled' => remote.enabled?
         | 
| 50 | 
            +
                            ]]
         | 
| 51 | 
            +
                        end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                        def add_remote(remote)
         | 
| 54 | 
            +
                            name, config = remote_to_config(remote)
         | 
| 55 | 
            +
                            targets = @ws.config.get('sync', Hash.new)
         | 
| 56 | 
            +
                            targets[name] = config
         | 
| 57 | 
            +
                            @ws.config.set('sync', targets)
         | 
| 58 | 
            +
                            @ws.save_config
         | 
| 59 | 
            +
                        end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                        def delete_remote(name)
         | 
| 62 | 
            +
                            targets = @ws.config.get('sync', Hash.new)
         | 
| 63 | 
            +
                            targets.delete(name)
         | 
| 64 | 
            +
                            @ws.config.set('sync', targets)
         | 
| 65 | 
            +
                            @ws.save_config
         | 
| 66 | 
            +
                        end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        def update_remote_config(name)
         | 
| 69 | 
            +
                            targets = @ws.config.get('sync', Hash.new)
         | 
| 70 | 
            +
                            unless (config = targets[name])
         | 
| 71 | 
            +
                                raise ArgumentError, "There is no target called #{name}"
         | 
| 72 | 
            +
                            end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                            new_config = config.dup
         | 
| 75 | 
            +
                            yield(new_config)
         | 
| 76 | 
            +
                            if new_config != config
         | 
| 77 | 
            +
                                targets[name] = new_config
         | 
| 78 | 
            +
                                @ws.config.set('sync', targets)
         | 
| 79 | 
            +
                                @ws.save_config
         | 
| 80 | 
            +
                            end
         | 
| 81 | 
            +
                        end
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
            end
         | 
| @@ -0,0 +1,399 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Autoproj
         | 
| 4 | 
            +
                module Sync
         | 
| 5 | 
            +
                    class Remote
         | 
| 6 | 
            +
                        class FailedRemoteCommand < RuntimeError; end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                        attr_reader :uri
         | 
| 9 | 
            +
                        attr_reader :name
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                        def self.from_uri(uri, name: uri, enabled: true)
         | 
| 12 | 
            +
                            if uri.scheme != "ssh"
         | 
| 13 | 
            +
                                raise ArgumentError, "unsupported protocol #{uri.scheme}"
         | 
| 14 | 
            +
                            end
         | 
| 15 | 
            +
                            Remote.new(uri, name: name, enabled: enabled)
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        def initialize(uri, name: uri, enabled: true)
         | 
| 19 | 
            +
                            @uri  = uri
         | 
| 20 | 
            +
                            @enabled = enabled
         | 
| 21 | 
            +
                            @name = name
         | 
| 22 | 
            +
                        end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                        def enabled?
         | 
| 25 | 
            +
                            @enabled
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        def remote_path
         | 
| 29 | 
            +
                            @uri.path
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                        def start
         | 
| 33 | 
            +
                            result = nil
         | 
| 34 | 
            +
                            Net::SFTP.start(@uri.host, @uri.user, password: @uri.password) do |sftp|
         | 
| 35 | 
            +
                                result = yield(sftp)
         | 
| 36 | 
            +
                            end
         | 
| 37 | 
            +
                            result
         | 
| 38 | 
            +
                        end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                        # Enumerate the packages that are outdated on the remote
         | 
| 41 | 
            +
                        #
         | 
| 42 | 
            +
                        # @yieldparam [Net::SFTP::Session] sftp the opened SFTP session, that can
         | 
| 43 | 
            +
                        #    be used to do further operations on the remote
         | 
| 44 | 
            +
                        # @yieldparam [Autoproj::PackageDescription] an outdated package
         | 
| 45 | 
            +
                        def each_outdated_package(sftp, ws, packages)
         | 
| 46 | 
            +
                            return enum_for(__method__, sftp, ws, packages) unless block_given?
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                            stat = packages.map do |package|
         | 
| 49 | 
            +
                                autobuild    = package.autobuild
         | 
| 50 | 
            +
                                installstamp = autobuild.installstamp
         | 
| 51 | 
            +
                                next unless File.exist?(installstamp)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                                local_stat  = File.stat(installstamp)
         | 
| 54 | 
            +
                                remote_path = File.join(uri.path, installstamp)
         | 
| 55 | 
            +
                                begin
         | 
| 56 | 
            +
                                    remote_stat = sftp.file.open(remote_path) do |f|
         | 
| 57 | 
            +
                                        f.stat
         | 
| 58 | 
            +
                                    end
         | 
| 59 | 
            +
                                    [package, local_stat, remote_stat, remote_path]
         | 
| 60 | 
            +
                                rescue Net::SFTP::StatusException => e
         | 
| 61 | 
            +
                                    if e.code != Net::SFTP::Constants::StatusCodes::FX_NO_SUCH_FILE
         | 
| 62 | 
            +
                                        raise
         | 
| 63 | 
            +
                                    end
         | 
| 64 | 
            +
                                    package
         | 
| 65 | 
            +
                                end
         | 
| 66 | 
            +
                            end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                            stat.compact.map do |package, local_stat, remote_stat, remote_path|
         | 
| 69 | 
            +
                                yield(package) if !local_stat ||
         | 
| 70 | 
            +
                                    changed_stat?(local_stat, remote_stat)
         | 
| 71 | 
            +
                            end.compact
         | 
| 72 | 
            +
                        end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        private def changed_stat?(local, remote)
         | 
| 75 | 
            +
                            return true if local.size != remote.size
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                            local_sec = local.mtime.tv_sec
         | 
| 78 | 
            +
                            local_usec = local.mtime.tv_usec
         | 
| 79 | 
            +
                            if remote.mtime != local_sec
         | 
| 80 | 
            +
                                true
         | 
| 81 | 
            +
                            elsif !remote.respond_to?(:remote_nseconds)
         | 
| 82 | 
            +
                                false
         | 
| 83 | 
            +
                            else
         | 
| 84 | 
            +
                                (remote.mtime_nseconds / 1000) != local_usec
         | 
| 85 | 
            +
                            end
         | 
| 86 | 
            +
                        end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                        private def remote_mkdir_p(sftp, local_path)
         | 
| 89 | 
            +
                            remote_path = File.join(@uri.path, local_path)
         | 
| 90 | 
            +
                            ops = []
         | 
| 91 | 
            +
                            while remote_path != '/'
         | 
| 92 | 
            +
                                ops << [remote_path, sftp.stat(remote_path)]
         | 
| 93 | 
            +
                                remote_path = File.dirname(remote_path)
         | 
| 94 | 
            +
                            end
         | 
| 95 | 
            +
                            missing = ops.take_while do |_, op|
         | 
| 96 | 
            +
                                op.wait
         | 
| 97 | 
            +
                                !op.response.ok?
         | 
| 98 | 
            +
                            end
         | 
| 99 | 
            +
                            mkdirs = missing.reverse.map do |path, _|
         | 
| 100 | 
            +
                                sftp.mkdir(path)
         | 
| 101 | 
            +
                            end
         | 
| 102 | 
            +
                            mkdirs.each(&:wait)
         | 
| 103 | 
            +
                        end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                        def autoproj_annex_files(ws)
         | 
| 106 | 
            +
                            user_files = %w[env.sh].
         | 
| 107 | 
            +
                                map do |file|
         | 
| 108 | 
            +
                                    File.join(ws.root_dir, file)
         | 
| 109 | 
            +
                                end
         | 
| 110 | 
            +
                            autoproj_files = %w[env.yml installation-manifest].
         | 
| 111 | 
            +
                                map do |file|
         | 
| 112 | 
            +
                                    File.join(ws.root_dir, '.autoproj', file)
         | 
| 113 | 
            +
                                end
         | 
| 114 | 
            +
                            bundler_files = %w[gems/Gemfile gems/Gemfile.lock].
         | 
| 115 | 
            +
                                map do |file|
         | 
| 116 | 
            +
                                    File.join(ws.prefix_dir, file)
         | 
| 117 | 
            +
                                end
         | 
| 118 | 
            +
                            [*user_files, *ws.env.source_before, *ws.env.source_after,
         | 
| 119 | 
            +
                                *autoproj_files, *bundler_files]
         | 
| 120 | 
            +
                        end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                        def rsync_target
         | 
| 123 | 
            +
                            if @uri.user && @uri.password
         | 
| 124 | 
            +
                                "#{@uri.user}:#{@uri.password}@#{@uri.host}"
         | 
| 125 | 
            +
                            elsif @uri.user
         | 
| 126 | 
            +
                                "#{@uri.user}@#{@uri.host}"
         | 
| 127 | 
            +
                            else
         | 
| 128 | 
            +
                                @uri.host
         | 
| 129 | 
            +
                            end
         | 
| 130 | 
            +
                        end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                        def rsync_dir(sftp, local_dir)
         | 
| 133 | 
            +
                            remote_dir = remote_path(local_dir)
         | 
| 134 | 
            +
                            ["rsync", "-a", "--delete-after", "#{local_dir}/",
         | 
| 135 | 
            +
                                "#{rsync_target}:#{remote_dir}/"]
         | 
| 136 | 
            +
                        end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                        def rsync_file(sftp, local_file)
         | 
| 139 | 
            +
                            remote_file = remote_path(local_file)
         | 
| 140 | 
            +
                            ["rsync", "-a", local_file,
         | 
| 141 | 
            +
                                "#{rsync_target}:#{remote_file}"]
         | 
| 142 | 
            +
                        end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                        def osdeps(sftp, ws, osdep_packages)
         | 
| 145 | 
            +
                            installer = ws.os_package_installer
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                            installer.setup_package_managers
         | 
| 148 | 
            +
                            all = ws.all_os_packages
         | 
| 149 | 
            +
                            partitioned_packages = installer.
         | 
| 150 | 
            +
                                resolve_and_partition_osdep_packages(osdep_packages, all)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                            os_packages = partitioned_packages.delete(installer.os_package_manager)
         | 
| 153 | 
            +
                            if os_packages
         | 
| 154 | 
            +
                                partitioned_packages = [[installer.os_package_manager, os_packages]].
         | 
| 155 | 
            +
                                    concat(partitioned_packages.to_a)
         | 
| 156 | 
            +
                            end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                            partitioned_packages = partitioned_packages.map do |manager, packages|
         | 
| 159 | 
            +
                                manager_name, _ = installer.package_managers.
         | 
| 160 | 
            +
                                    find { |key, obj| manager == obj }
         | 
| 161 | 
            +
                                [manager_name, packages]
         | 
| 162 | 
            +
                            end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                            partitioned_packages.each do |manager_name, packages|
         | 
| 165 | 
            +
                                install_osdep_packages(sftp, ws, manager_name, packages)
         | 
| 166 | 
            +
                            end
         | 
| 167 | 
            +
                        end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                        def install_osdep_packages(sftp, ws, manager_name, packages)
         | 
| 170 | 
            +
                            Autobuild.progress_start "sync-#{name}-osdeps-#{manager_name}",
         | 
| 171 | 
            +
                                "sync: handling #{packages.size} osdeps #{manager_name} packages "\
         | 
| 172 | 
            +
                                    "on #{name}",
         | 
| 173 | 
            +
                                done_message: "sync: handled #{packages.size} "\
         | 
| 174 | 
            +
                                    "osdeps #{manager_name} packages on #{name}" do
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                                result = remote_autoproj(sftp, ws.root_dir,
         | 
| 177 | 
            +
                                    "sync", "install-osdeps",
         | 
| 178 | 
            +
                                    manager_name, *packages)
         | 
| 179 | 
            +
                                if result[:exit_code] != 0
         | 
| 180 | 
            +
                                    raise RuntimeError,
         | 
| 181 | 
            +
                                        "remote autoproj command failed\n"\
         | 
| 182 | 
            +
                                        "autoproj exited with status "\
         | 
| 183 | 
            +
                                        "#{result[:exit_code]}\n#{result}"
         | 
| 184 | 
            +
                                end
         | 
| 185 | 
            +
                            end
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                        def create_package_directories(sftp, pkg)
         | 
| 189 | 
            +
                            Autobuild.progress_start pkg, "sync: preparing #{pkg.name}@#{name}",
         | 
| 190 | 
            +
                                done_message: "sync: prepared #{pkg.name}@#{name}" do
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                                remote_mkdir_p(sftp, pkg.autobuild.prefix)
         | 
| 193 | 
            +
                                remote_mkdir_p(sftp, File.dirname(pkg.autobuild.installstamp))
         | 
| 194 | 
            +
                            end
         | 
| 195 | 
            +
                        end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                        def rsync_package(sftp, pkg)
         | 
| 198 | 
            +
                            Autobuild.progress_start pkg, "sync: updating #{pkg.name}@#{name}",
         | 
| 199 | 
            +
                                done_message: "sync: updated #{pkg.name}@#{name}" do
         | 
| 200 | 
            +
                                ops = [rsync_dir(sftp, pkg.autobuild.prefix),
         | 
| 201 | 
            +
                                    rsync_file(sftp, pkg.autobuild.installstamp)]
         | 
| 202 | 
            +
                                ops.each do |op|
         | 
| 203 | 
            +
                                    if !system(*op)
         | 
| 204 | 
            +
                                        raise "update of #{pkg.name} failed"
         | 
| 205 | 
            +
                                    end
         | 
| 206 | 
            +
                                end
         | 
| 207 | 
            +
                            end
         | 
| 208 | 
            +
                        end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                        def remote_path(local_path)
         | 
| 211 | 
            +
                            File.join(@uri.path, local_path)
         | 
| 212 | 
            +
                        end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                        def remote_file_exist?(sftp, path)
         | 
| 215 | 
            +
                            sftp.stat!(remote_path(path))
         | 
| 216 | 
            +
                            true
         | 
| 217 | 
            +
                        rescue Net::SFTP::StatusException => e
         | 
| 218 | 
            +
                            if e.code == Net::SFTP::Constants::StatusCodes::FX_NO_SUCH_FILE
         | 
| 219 | 
            +
                                false
         | 
| 220 | 
            +
                            else
         | 
| 221 | 
            +
                                raise
         | 
| 222 | 
            +
                            end
         | 
| 223 | 
            +
                        end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                        def remote_file_get(sftp, local_path)
         | 
| 226 | 
            +
                            sftp.download!(remote_path(local_path))
         | 
| 227 | 
            +
                        end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
                        def remote_file_put(sftp, local_path, content)
         | 
| 230 | 
            +
                            sftp.upload!(StringIO.new(content), remote_path(local_path))
         | 
| 231 | 
            +
                        end
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                        def remote_file_transfer(sftp, local_path, target: remote_path(local_path))
         | 
| 234 | 
            +
                            sftp.upload!(local_path, target)
         | 
| 235 | 
            +
                        end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                        def remote_autoproj(sftp, root_dir, *command, chdir: nil, interactive: false)
         | 
| 238 | 
            +
                            remote_exec(sftp,
         | 
| 239 | 
            +
                                remote_path(File.join(root_dir, ".autoproj/bin/autoproj")),
         | 
| 240 | 
            +
                                *command, chdir: chdir, interactive: interactive)
         | 
| 241 | 
            +
                        end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                        def remote_exec(sftp, *command, chdir: nil, interactive: false)
         | 
| 244 | 
            +
                            if interactive
         | 
| 245 | 
            +
                                remote_interactive_exec(sftp, *command, chdir: chdir)
         | 
| 246 | 
            +
                            else
         | 
| 247 | 
            +
                                status = Hash.new
         | 
| 248 | 
            +
                                ios = Hash[:stdout => STDOUT, :stderr => STDOUT]
         | 
| 249 | 
            +
                                target_dir = @uri.path
         | 
| 250 | 
            +
                                target_dir = File.join(target_dir, chdir) if chdir
         | 
| 251 | 
            +
                                pid = nil
         | 
| 252 | 
            +
                                command = "cd '#{target_dir}' && "\
         | 
| 253 | 
            +
                                    "echo \"AUTOPROJ_SYNC_PID=$$\" && "\
         | 
| 254 | 
            +
                                    "exec '" + command.join("' '") + "'"
         | 
| 255 | 
            +
                                ch = sftp.session.exec(command, status: status) do |channel, stream, data|
         | 
| 256 | 
            +
                                    if !pid && (m = /^AUTOPROJ_SYNC_PID=(\d+)/.match(data))
         | 
| 257 | 
            +
                                        pid = Integer(m[1])
         | 
| 258 | 
            +
                                    else
         | 
| 259 | 
            +
                                        ios[stream].print(data)
         | 
| 260 | 
            +
                                        ios[stream].flush
         | 
| 261 | 
            +
                                    end
         | 
| 262 | 
            +
                                end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                                begin
         | 
| 265 | 
            +
                                    ch.wait
         | 
| 266 | 
            +
                                    status
         | 
| 267 | 
            +
                                rescue Interrupt
         | 
| 268 | 
            +
                                    sftp.session.exec!("kill #{pid}") if pid
         | 
| 269 | 
            +
                                    ch.wait
         | 
| 270 | 
            +
                                    raise
         | 
| 271 | 
            +
                                end
         | 
| 272 | 
            +
                            end
         | 
| 273 | 
            +
                        end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                        def remote_interactive_exec(sftp, *command, chdir: nil)
         | 
| 276 | 
            +
                            channel = sftp.session.open_channel do |ch|
         | 
| 277 | 
            +
                                ch.on_data do |ch, data|
         | 
| 278 | 
            +
                                    STDOUT.print data
         | 
| 279 | 
            +
                                    STDOUT.flush
         | 
| 280 | 
            +
                                end
         | 
| 281 | 
            +
                                ch.on_extended_data do |ch, type, data|
         | 
| 282 | 
            +
                                    STDERR.print data
         | 
| 283 | 
            +
                                end
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                                ch.request_pty
         | 
| 286 | 
            +
                                ch.exec("cd '#{chdir}' && '" + command.join("' '") + "'")
         | 
| 287 | 
            +
                            end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                            ssh = sftp.session
         | 
| 290 | 
            +
                            while channel.active?
         | 
| 291 | 
            +
                                ssh.process(0.1)
         | 
| 292 | 
            +
                                begin
         | 
| 293 | 
            +
                                    while true
         | 
| 294 | 
            +
                                        data = STDIN.read_nonblock(4096)
         | 
| 295 | 
            +
                                        channel.send_data(data)
         | 
| 296 | 
            +
                                    end
         | 
| 297 | 
            +
                                rescue IO::WaitReadable
         | 
| 298 | 
            +
                                end
         | 
| 299 | 
            +
                            end
         | 
| 300 | 
            +
                        rescue EOFError
         | 
| 301 | 
            +
                            channel.close
         | 
| 302 | 
            +
                        end
         | 
| 303 | 
            +
             | 
| 304 | 
            +
                        def local_file_get(local_path)
         | 
| 305 | 
            +
                            File.read(local_path)
         | 
| 306 | 
            +
                        end
         | 
| 307 | 
            +
             | 
| 308 | 
            +
                        def info(message)
         | 
| 309 | 
            +
                            Autoproj.message "  #{message}", force: true
         | 
| 310 | 
            +
                        end
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                        def bootstrap_or_update_autoproj(sftp, ws)
         | 
| 313 | 
            +
                            gemfile_lock_path = File.join(ws.root_dir, ".autoproj/Gemfile.lock")
         | 
| 314 | 
            +
                            if remote_file_exist?(sftp, gemfile_lock_path)
         | 
| 315 | 
            +
                                remote_gemfile_lock = remote_file_get(sftp, gemfile_lock_path)
         | 
| 316 | 
            +
                                local_gemfile_lock  = local_file_get(gemfile_lock_path)
         | 
| 317 | 
            +
                                if remote_gemfile_lock == local_gemfile_lock
         | 
| 318 | 
            +
                                    info "remote Autoproj install up-to-date"
         | 
| 319 | 
            +
                                    info "sync: Autoproj install up-to-date on #{name}"
         | 
| 320 | 
            +
                                    return
         | 
| 321 | 
            +
                                end
         | 
| 322 | 
            +
             | 
| 323 | 
            +
                                info "sync: updating the Autoproj install on #{name}"
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                                remote_file_put(sftp, gemfile_lock_path, local_gemfile_lock)
         | 
| 326 | 
            +
                                remote_file_transfer(
         | 
| 327 | 
            +
                                    sftp, File.join(ws.root_dir, ".autoproj/Gemfile"))
         | 
| 328 | 
            +
                                remote_file_transfer(
         | 
| 329 | 
            +
                                    sftp, File.join(ws.root_dir, ".autoproj/config.yml"))
         | 
| 330 | 
            +
                                result = remote_autoproj(
         | 
| 331 | 
            +
                                    sftp, ws.root_dir, "update", "--autoproj")
         | 
| 332 | 
            +
                                unless result[:exit_code] == 0
         | 
| 333 | 
            +
                                    raise FailedRemoteCommand, "failed to update Autoproj:\n"\
         | 
| 334 | 
            +
                                        "autoproj update --autoproj finished with exit status "\
         | 
| 335 | 
            +
                                        "#{result[:exit_code]}\n"\
         | 
| 336 | 
            +
                                        "#{result}"
         | 
| 337 | 
            +
                                end
         | 
| 338 | 
            +
                            else
         | 
| 339 | 
            +
                                info "sync: installing Autoproj on #{name}"
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                                autoproj_spec = Bundler.definition.specs.
         | 
| 342 | 
            +
                                    find { |spec| spec.name == "autoproj" }
         | 
| 343 | 
            +
                                autoproj_dir = autoproj_spec.full_gem_path
         | 
| 344 | 
            +
                                install_script = File.join(autoproj_dir, "bin", "autoproj_install")
         | 
| 345 | 
            +
                                remote_mkdir_p(sftp, ws.root_dir)
         | 
| 346 | 
            +
                                sftp.upload!(install_script,
         | 
| 347 | 
            +
                                    remote_path(File.join(ws.root_dir, "autoproj_install")))
         | 
| 348 | 
            +
                                remote_file_transfer(sftp, File.join(ws.root_dir, ".autoproj/config.yml"),
         | 
| 349 | 
            +
                                    target: remote_path(File.join(ws.root_dir, 'bootstrap-config.yml')))
         | 
| 350 | 
            +
                                remote_file_transfer(sftp, File.join(ws.root_dir, ".autoproj/Gemfile"),
         | 
| 351 | 
            +
                                    target: remote_path(File.join(ws.root_dir, 'bootstrap-Gemfile')))
         | 
| 352 | 
            +
                                result = sftp.session.exec!("cd '#{remote_path(ws.root_dir)}' && "\
         | 
| 353 | 
            +
                                    "#{ws.config.ruby_executable} autoproj_install "\
         | 
| 354 | 
            +
                                    "--gemfile bootstrap-Gemfile "\
         | 
| 355 | 
            +
                                    "--seed-config bootstrap-config.yml")
         | 
| 356 | 
            +
                                if result.exitstatus != 0
         | 
| 357 | 
            +
                                    raise RuntimeError, "failed to install autoproj: #{result}"
         | 
| 358 | 
            +
                                end
         | 
| 359 | 
            +
                            end
         | 
| 360 | 
            +
                        end
         | 
| 361 | 
            +
             | 
| 362 | 
            +
                        def update(sftp, ws, packages)
         | 
| 363 | 
            +
                            # First check if autoproj is bootstrapped on the target already
         | 
| 364 | 
            +
                            bootstrap_or_update_autoproj(sftp, ws)
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                            packages = each_outdated_package(sftp, ws, packages).to_a
         | 
| 367 | 
            +
             | 
| 368 | 
            +
                            info "sync: #{packages.size} outdated packages on #{name}"
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                            executor = Concurrent::FixedThreadPool.new(6)
         | 
| 371 | 
            +
                            futures = packages.map do |pkg|
         | 
| 372 | 
            +
                                create_package_directories(sftp, pkg)
         | 
| 373 | 
            +
                                Concurrent::Future.execute(executor: executor) do
         | 
| 374 | 
            +
                                    rsync_package(sftp, pkg)
         | 
| 375 | 
            +
                                end
         | 
| 376 | 
            +
                            end
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                            # Copy some autoproj installation-manifest files
         | 
| 379 | 
            +
                            Autobuild.progress_start "sync-#{name}-autoproj",
         | 
| 380 | 
            +
                                "sync: updating Autoproj configuration files on #{name}",
         | 
| 381 | 
            +
                                done_message: "sync: updated Autoproj configuration files on #{name}" do
         | 
| 382 | 
            +
                                autoproj_annex_files(ws).each do |file|
         | 
| 383 | 
            +
                                    sftp.upload!(file, File.join(@uri.path, file))
         | 
| 384 | 
            +
                                end
         | 
| 385 | 
            +
                            end
         | 
| 386 | 
            +
             | 
| 387 | 
            +
                            futures.each_with_index do |f, i|
         | 
| 388 | 
            +
                                f.value!
         | 
| 389 | 
            +
                            end
         | 
| 390 | 
            +
             | 
| 391 | 
            +
                        ensure
         | 
| 392 | 
            +
                            if executor
         | 
| 393 | 
            +
                                executor.shutdown
         | 
| 394 | 
            +
                                executor.wait_for_termination
         | 
| 395 | 
            +
                            end
         | 
| 396 | 
            +
                        end
         | 
| 397 | 
            +
                    end
         | 
| 398 | 
            +
                end
         | 
| 399 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,113 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: autoproj-sync
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Sylvain Joyeux
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: exe
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2018-11-14 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: autoproj
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '2.4'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '2.4'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: net-sftp
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ">="
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '0'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ">="
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: bundler
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '1.16'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '1.16'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: minitest
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '5.0'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '5.0'
         | 
| 69 | 
            +
            description: 
         | 
| 70 | 
            +
            email:
         | 
| 71 | 
            +
            - sylvain.joyeux@13robotics.com
         | 
| 72 | 
            +
            executables: []
         | 
| 73 | 
            +
            extensions: []
         | 
| 74 | 
            +
            extra_rdoc_files: []
         | 
| 75 | 
            +
            files:
         | 
| 76 | 
            +
            - ".gitignore"
         | 
| 77 | 
            +
            - ".travis.yml"
         | 
| 78 | 
            +
            - Gemfile
         | 
| 79 | 
            +
            - LICENSE.txt
         | 
| 80 | 
            +
            - README.md
         | 
| 81 | 
            +
            - Rakefile
         | 
| 82 | 
            +
            - autoproj-sync.gemspec
         | 
| 83 | 
            +
            - lib/autoproj-sync.rb
         | 
| 84 | 
            +
            - lib/autoproj/cli/main_sync.rb
         | 
| 85 | 
            +
            - lib/autoproj/sync.rb
         | 
| 86 | 
            +
            - lib/autoproj/sync/config.rb
         | 
| 87 | 
            +
            - lib/autoproj/sync/remote.rb
         | 
| 88 | 
            +
            - lib/autoproj/sync/version.rb
         | 
| 89 | 
            +
            homepage: https://rock-core.github.io/rock-and-syskit
         | 
| 90 | 
            +
            licenses:
         | 
| 91 | 
            +
            - MIT
         | 
| 92 | 
            +
            metadata: {}
         | 
| 93 | 
            +
            post_install_message: 
         | 
| 94 | 
            +
            rdoc_options: []
         | 
| 95 | 
            +
            require_paths:
         | 
| 96 | 
            +
            - lib
         | 
| 97 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 98 | 
            +
              requirements:
         | 
| 99 | 
            +
              - - ">="
         | 
| 100 | 
            +
                - !ruby/object:Gem::Version
         | 
| 101 | 
            +
                  version: '0'
         | 
| 102 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 103 | 
            +
              requirements:
         | 
| 104 | 
            +
              - - ">="
         | 
| 105 | 
            +
                - !ruby/object:Gem::Version
         | 
| 106 | 
            +
                  version: '0'
         | 
| 107 | 
            +
            requirements: []
         | 
| 108 | 
            +
            rubyforge_project: 
         | 
| 109 | 
            +
            rubygems_version: 2.7.6
         | 
| 110 | 
            +
            signing_key: 
         | 
| 111 | 
            +
            specification_version: 4
         | 
| 112 | 
            +
            summary: Synchronizes the build byproducts of an Autoproj workspace to remote target(s)
         | 
| 113 | 
            +
            test_files: []
         |