def setup_new_build(root_repo, name): with NewDirectory(name): banner('Bootstrapping checkout build') muddle(['bootstrap', 'git+%s' % root_repo, 'test_build']) cat('src/builds/01.py') banner('Setting up src/') with Directory('src'): with Directory('builds'): touch('01.py', CHECKOUT_BUILD_LEVELS) # Then remove the .pyc file, because Python probably won't realise # that this new 01.py is later than the previous version os.remove('01.pyc') git('add 01.py') git('commit -m "New build"') git('push %s/builds HEAD' % root_repo) with NewDirectory('checkout1'): touch('Makefile.muddle', MUDDLE_MAKEFILE) git('init') git('add Makefile.muddle') git('commit -m "Add muddle makefile"') # Assert that this checkout *has* been checked out # (this also does a "muddle reparent" for us, setting # up our remote origin for pushing) muddle(['import']) # And thus we can now ask muddle to push for us # (although a plain "git push" would also work) muddle(['push']) with NewDirectory('twolevel'): with NewDirectory('checkout2'): touch('Makefile.muddle', MUDDLE_MAKEFILE) git('init') git('add Makefile.muddle') git('commit -m "Add muddle makefile"') muddle(['import']) # As was said in 'checkout1', we can "git push" if # we prefer, once we've done import - although we do # need to be specific about *what* we're pushing, this # first time round git('push origin master') with NewDirectory('multilevel'): with NewDirectory('inner'): with NewDirectory('checkout3'): touch('Makefile.muddle', MUDDLE_MAKEFILE) git('init') git('add Makefile.muddle') git('commit -m "Add muddle makefile"') # Or we can do a more complicated sequence of things # Just assert checkout via its tagged label muddle(['assert', 'checkout:alice/checked_out']) # then reparent by hand muddle(['reparent']) # and now we can git push git('push origin master')
def change_something_else_and_push(root_dir, d): repo = os.path.join(root_dir, 'repo') with Directory('domains'): with Directory('sub1'): with Directory('src'): with Directory('co0'): append('sub1.c', '\n// A harmless change\n') git('commit sub1.c -m "A harmless change"') # We'd better be able to push with muddle! muddle(['push'])
def amend_sub1_build_desc_and_push(root_dir, d): repo = os.path.join(root_dir, 'repo') with Directory('domains'): with Directory('sub1'): with Directory('src'): with Directory('builds'): append('01.py', '\n# A harmless change\n') git('commit 01.py -m "A harmless change"') # We'd better push with git, since we've hacked the build description git('push origin HEAD')
def test_stamp_is_current_working_set(first_stamp): """Check we are stamping the current working set """ with NewCountedDirectory('build3') as d2: banner('TESTING STAMP CURRENT WORKING SET') muddle(['unstamp', first_stamp]) # So, we've selected specific revisions for all of our checkouts # and thus they are all in "detached HEAD" state revisions = capture_revisions() # XXX To be considered XXX # Here, we are deliberately making a change that we do not push to the # remote repository. Thus our stamp file will contain a revision id # that no-one else can make sense of. This may be a Bad Thing. # Indeed, if we try to do this with a bzr repository, our own code # in 'muddle query checkout-id' would use 'bzr missing' and notice that # the revision was not present at the far end, and give up with a # complaint at that point. with Directory('src'): with Directory('first_co'): append('Makefile.muddle', '\n# A comment\n') git('commit Makefile.muddle -m "Add a comment"') # Don't forget that ".strip()" to remove the trailing newline! first_co_rev2 = captured_muddle(['query', 'checkout-id', 'first_co']).strip() if first_co_rev2 == revisions['first_co']: raise GiveUp('The revision of first_co did not change') revisions['first_co'] = first_co_rev2 muddle(['stamp', 'save', 'amended.stamp']) stamp = VersionStamp.from_file('amended.stamp') if len(stamp.checkouts) != len(revisions): raise GiveUp('Stamp file has %d checkouts, build tree %d' % (len(stamp.checkouts), len(revisions))) for co in stamp.checkouts: if co.domain: dom_plus_name = '(%s)%s' % (co.domain, co.name) else: dom_plus_name = co.name repo = stamp.checkouts[co][ -1] # ah, named tuples would be good here #print dom_plus_name #print ' S:',repo.revision #print ' D:',revisions[dom_plus_name] if repo.revision != revisions[dom_plus_name]: raise GiveUp( 'Checkout %s is revision %s in stamp file,' ' %s on disk' % (dom_plus_name, repo.revision, revisions[dom_plus_name]))
def test_svn_simple_build(): """Bootstrap a muddle build tree. """ root_dir = normalise_dir(os.getcwd()) with NewDirectory('repo'): for name in ('main', 'versions'): shell('svnadmin create %s' % name) print 'Repositories are:', ' '.join(os.listdir('.')) root_repo = 'file://' + os.path.join(root_dir, 'repo', 'main') versions_repo = 'file://' + os.path.join(root_dir, 'repo', 'versions') with NewDirectory('test_build1'): banner('Bootstrapping simple build') muddle(['bootstrap', 'svn+%s' % root_repo, 'test_build']) cat('src/builds/01.py') # But, of course, we don't keep the versions/ directory in the same # repository (lest things get very confused) touch('.muddle/VersionsRepository', 'svn+%s\n' % versions_repo) with Directory('versions'): touch('fred.stamp', '# A comment\n# Another comment\n') svn('import . %s -m "Initial import"' % versions_repo) # Is the next really the best we can do? shell('rm -rf versions') svn('checkout %s' % versions_repo) with Directory('src'): with Directory('builds'): svn('import . %s/builds -m "Initial import"' % root_repo) # Is the next really the best we can do? shell('rm -rf builds') svn('checkout %s/builds' % root_repo) banner('Stamping simple build') muddle(['stamp', 'version']) with Directory('versions'): svn('commit -m "A proper stamp file"') cat('test_build.stamp') # We should be able to check everything out from the repository with NewDirectory('test_build2'): banner('Building from init') muddle(['init', 'svn+%s' % root_repo, 'builds/01.py']) muddle(['checkout', '_all']) # We should be able to recreate our state from the stamp file... with NewDirectory('test_build3'): banner('Unstamping simple build') # Note that we do not ask for 'versions/test_build.stamp', since # our repository corresponds to this versions/ directory as a whole... muddle(['unstamp', 'svn+%s' % versions_repo, 'test_build.stamp'])
def test_git_simple_build(): """Bootstrap a muddle build tree. """ root_dir = normalise_dir(os.getcwd()) with NewDirectory('repo'): for name in ('builds', 'versions'): with NewDirectory(name): git('init --bare') print 'Repositories are:', ' '.join(os.listdir('.')) root_repo = 'file://' + os.path.join(root_dir, 'repo') with NewDirectory('test_build1'): banner('Bootstrapping simple build') muddle(['bootstrap', 'git+%s' % root_repo, 'test_build']) cat('src/builds/01.py') with Directory('versions'): touch('fred.stamp', '# A comment\n# Another comment\n') git('add fred.stamp') git('commit -m "New stamp file"') # "muddle stamp push" knows how to setup the appropriate # repository and push - i.e., if effectively does # # git('remote add origin %s/versions'%root_repo) # git('push origin master') # muddle(['stamp', 'push']) with Directory('src/builds'): git('commit -m "New build"') muddle(['push']) banner('Stamping simple build') muddle(['stamp', 'version']) with Directory('versions'): # Muddle should already have done "git add" for us # git('add test_build.stamp') git('commit -m "A proper stamp file"') cat('test_build.stamp') muddle(['stamp', 'push']) # We should be able to check everything out from the repository with NewDirectory('test_build2'): banner('Building from init') muddle(['init', 'git+%s' % root_repo, 'builds/01.py']) muddle(['checkout', '_all']) # We should be able to recreate our state from the stamp file... with NewDirectory('test_build3'): banner('Unstamping simple build') muddle(['unstamp', 'git+%s' % root_repo, 'versions/test_build.stamp'])
def test_git_checkout_build(): """Test single, twolevel and multilevel checkouts. Relies on setup_git_checkout_repositories() having been called. """ root_dir = normalise_dir(os.getcwd()) root_repo = 'file://' + os.path.join(root_dir, 'repo') setup_new_build(root_repo, 'test_build1') with Directory('test_build1'): banner('Stamping checkout build') muddle(['stamp', 'version']) with Directory('versions'): git('add checkout_test.stamp') git('commit -m "A stamp file"') # We have to associate it with a repository git('remote add origin %s/versions' % root_repo) git('push origin master') cat('checkout_test.stamp') # We should be able to use muddle to push the stamp file muddle(['stamp', 'push']) # We should be able to check everything out from the repository with NewDirectory('test_build2'): banner('Building checkout build from init') muddle(['init', 'git+%s' % root_repo, 'builds/01.py']) muddle(['checkout', '_all']) check_files([ 'src/builds/01.py', 'src/checkout1/Makefile.muddle', 'src/twolevel/checkout2/Makefile.muddle', 'src/multilevel/inner/checkout3/Makefile.muddle', ]) # We should be able to recreate our state from the stamp file... with NewDirectory('test_build3'): banner('Unstamping checkout build') muddle( ['unstamp', 'git+%s' % root_repo, 'versions/checkout_test.stamp']) check_files([ 'src/builds/01.py', 'versions/checkout_test.stamp', 'src/checkout1/Makefile.muddle', 'src/twolevel/checkout2/Makefile.muddle', 'src/multilevel/inner/checkout3/Makefile.muddle', ])
def make_old_build_tree(): """Make a build tree that deploys a CPIO file, and use/test it """ with NewDirectory('build.old') as d: muddle(['bootstrap', 'git+file:///nowhere', 'cpio-test-build']) with Directory('src'): with Directory('builds'): touch('01.py', DEPLOYMENT_BUILD_DESC) # Then remove the .pyc file, because Python probably won't realise # that this new 01.py is later than the previous version os.remove('01.pyc') with NewDirectory('first_co'): git('init') touch('Makefile.muddle', MUDDLE_MAKEFILE1.format(progname='program1')) touch('program1.c', MAIN_C_SRC.format(progname='program1')) touch('instructions.xml', INSTRUCTIONS) git('add Makefile.muddle program1.c instructions.xml') git('commit -m "A commit"') muddle(['import']) with NewDirectory('second_co'): git('init') touch( 'Makefile.muddle', MUDDLE_MAKEFILE2.format(progname1='program1', progname2='program2')) touch('program2.c', MAIN_C_SRC.format(progname='program2')) # A version of program1 that announces itself as program2 touch('program1.c', MAIN_C_SRC.format(progname='program2')) git('add Makefile.muddle program2.c') git('commit -m "A commit"') muddle(['import']) muddle([]) with Directory('deploy'): with Directory('everything'): check_cpio_archive('firmware.cpio') # Check we got the correct version of program1 extract_file('firmware.cpio', '/bin/program1') text = get_stdout('bin/program1') if text != 'Program program2\n': raise GiveUp( 'Expected the program1 from role2, but it output %s' % text) print 'That looks like the correct program1'
def show_version(): """Show something akin to a version of this muddle. Simply run git to do it for us. Of course, this will fail if we don't have git... """ this_dir = os.path.split(__file__)[0] muddle_dir = os.path.split(this_dir)[0] cmd_tag = ['git', 'describe', '--dirty=-modified', '--long', '--tags'] cmd_all = ['git', 'describe', '--dirty=-modified', '--long', '--all'] branch = ['git', 'symbolic-ref', '-q', 'HEAD'] version = "<unknown>" with Directory(muddle_dir, show_pushd=False): # First try looking for a version using tags, which should normally # work. version = our_cmd(cmd_tag, error_ok=True) # If that failed, try with --all if not version: version = our_cmd(cmd_tag, error_ok=False) # Are we on a branch? branch = our_cmd(branch) if branch: if branch.startswith('refs/heads/'): branch = branch[11:] if branch == 'master': branch = None if branch: print '%s on branch %s in %s'%(version, branch, muddle_dir) else: print '%s in %s'%(version, muddle_dir)
def checkout(self, repo, co_leaf, options, verbose=True): """ Clone a given checkout. Will be called in the parent directory of the checkout. Expected to create a directory called <co_leaf> therein. """ if repo.branch: args = ["-b", repo.branch] else: # Explicitly use master if no branch specified - don't default args = ["-b", "master"] if options.get('shallow_checkout'): args += ["--depth", "1"] utils.shell(["git", "clone"] + args + [repo.url, str(co_leaf)], show_command=verbose) if repo.revision: with Directory(co_leaf): # Are we already at the correct revision? actual_revision = self._git_rev_parse_HEAD() if actual_revision != expand_revision(repo.revision): # XXX Arguably, should use '--quiet', to suppress the warning # XXX that we are ending up in 'detached HEAD' state, since # XXX that is rather what we asked for... # XXX Or maybe we want to leave the message, as the warning # XXX it is meant to be utils.shell(["git", "checkout", repo.revision])
def get_current_branch(self, builder, co_label, verbose=False, show_pushd=False): """ Return the name of the current branch. Will be called in the actual checkout's directory. Return the name of the current branch (e.g., "master" or "Fred"), or None if there is no current branch. If 'show_pushd' is false, then we won't report as we "pushd" into the checkout directory. Raises a GiveUp exception if the VCS does not support this operation, or if something goes wrong. """ try: with Directory(builder.db.get_checkout_path(co_label), show_pushd=show_pushd): return self.vcs.get_current_branch() except (GiveUp, Unsupported) as err: raise GiveUp( 'Failure getting current branch for %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err))
def goto_revision(self, builder, co_label, revision, branch=None, verbose=False, show_pushd=False): """ Go to the specified revision. If branch is given, this may alter the behaviour. Will be called in the actual checkout's directory. If 'show_pushd' is false, then we won't report as we "pushd" into the checkout directory. """ repo = builder.db.get_checkout_repo(co_label) try: with Directory(builder.db.get_checkout_path(co_label), show_pushd=show_pushd): return self.vcs.goto_revision(revision, branch, repo, verbose) except (GiveUp, Unsupported) as err: if branch: raise GiveUp( 'Failure changing to revision %s, branch %s, for %s in %s:\n%s' % (revision, branch, co_label, builder.db.get_checkout_location(co_label), err)) else: raise GiveUp( 'Failure changing to revision %s for %s in %s:\n%s' % (revision, co_label, builder.db.get_checkout_location(co_label), err))
def branch_exists(self, builder, co_label, branch, verbose=False, show_pushd=False): """ Returns True if a branch of that name exists. This allowed to be conservative - e.g., in git the existence of a remote branch with the given name can be counted as True. Will be called in the actual checkout's directory. If 'show_pushd' is false, then we won't report as we "pushd" into the checkout directory. """ try: with Directory(builder.db.get_checkout_path(co_label), show_pushd=show_pushd): return self.vcs.branch_exists(branch) except (GiveUp, Unsupported) as err: raise GiveUp( 'Failure checking existence of branch %s for %s in %s:\n%s' % (branch, co_label, builder.db.get_checkout_location(co_label), err))
def test_bzr_simple_build(): """Bootstrap a muddle build tree. """ root_dir = normalise_dir(os.getcwd()) with NewDirectory('repo'): for name in ('builds', 'versions'): with NewDirectory(name): bzr('init') print 'Repositories are:', ' '.join(os.listdir('.')) root_repo = 'file://' + os.path.join(root_dir, 'repo') with NewDirectory('test_build1'): banner('Bootstrapping simple build') muddle(['bootstrap', 'bzr+%s' % root_repo, 'test_build']) cat('src/builds/01.py') with Directory('versions'): touch('fred.stamp', '# A comment\n# Another comment\n') bzr('add fred.stamp') bzr('commit -m "New stamp file"') # We should be able to use muddle to push the stamp file muddle(['stamp', 'push']) with Directory('src/builds'): bzr('commit -m "New build"') muddle(['push']) banner('Stamping simple build') muddle(['stamp', 'version']) with Directory('versions'): # Muddle should have added the stamp file to bzr for us bzr('commit -m "A proper stamp file"') cat('test_build.stamp') muddle(['stamp', 'push']) # We should be able to check everything out from the repository with NewDirectory('test_build2'): banner('Building from init') muddle(['init', 'bzr+%s' % root_repo, 'builds/01.py']) muddle(['checkout', '_all']) # We should be able to recreate our state from the stamp file... with NewDirectory('test_build3'): banner('Unstamping simple build') muddle(['unstamp', 'bzr+%s' % root_repo, 'versions/test_build.stamp'])
def setup_new_build(root_repo, name): with NewDirectory('test_build1'): banner('Bootstrapping checkout build') muddle(['bootstrap', 'bzr+%s' % root_repo, 'test_build']) cat('src/builds/01.py') banner('Setting up src/') with Directory('src'): with Directory('builds'): touch('01.py', CHECKOUT_BUILD_LEVELS) # Then remove the .pyc file, because Python probably won't realise # that this new 01.py is later than the previous version os.remove('01.pyc') bzr('add 01.py') bzr('commit -m "New build"') # The obvious thing to do is to push with muddle muddle(['push']) with NewDirectory('checkout1'): touch('Makefile.muddle', MUDDLE_MAKEFILE) bzr('init') bzr('add Makefile.muddle') bzr('commit -m "Add muddle makefile"') muddle(['import', 'checkout1']) # Or we can push by hand with bzr bzr('push %s/checkout1' % root_repo) with NewDirectory('twolevel'): with NewDirectory('checkout2'): touch('Makefile.muddle', MUDDLE_MAKEFILE) bzr('init') bzr('add Makefile.muddle') bzr('commit -m "Add muddle makefile"') muddle(['import', 'checkout2']) muddle(['push']) with NewDirectory('multilevel'): with NewDirectory('inner'): with NewDirectory('checkout3'): touch('Makefile.muddle', MUDDLE_MAKEFILE) bzr('init') bzr('add Makefile.muddle') bzr('commit -m "Add muddle makefile"') # If we push directly with BZR bzr('push %s/multilevel/inner/checkout3' % root_repo) muddle(['import', 'alice'])
def test_stamping_branches(repo): """Test we cope with branches. """ banner('TESTING STAMPING BRANCHES') with NewCountedDirectory('build6') as d: muddle([ 'init', 'git+file://{repo}/main'.format(repo=repo), 'builds/01.py' ]) muddle(['checkout', '_all']) with Directory('src'): with Directory('first_co'): git('checkout -b branch1') muddle(['push']) muddle(['stamp', 'save', '01.stamp']) with NewCountedDirectory('build7'): muddle(['unstamp', d.join('01.stamp')]) # We should have got the branch for first_co, even though it's not # the branch in the build description # Check we're working with the expected branches text = captured_muddle(['query', 'checkout-branches']) lines = text.splitlines() lines = lines[3:] # ignore the header lines check_text_lines_v_lines(lines, [ "builds master <none> <not following>", "first_co branch1 <none> <not following>", "main_co master <none> <not following>", "second_co <not supported> ... ...", "(subdomain1)builds master <none> <not following>", "(subdomain1)first_co master <none> <not following>", "(subdomain1)main_co master <none> <not following>", "(subdomain1)second_co master <none> <not following>", "(subdomain1(subdomain3))builds master <none> <not following>", "(subdomain1(subdomain3))first_co master <none> <not following>", "(subdomain1(subdomain3))main_co master <none> <not following>", "(subdomain1(subdomain3))second_co master <none> <not following>", "(subdomain2)builds master <none> <not following>", "(subdomain2)first_co master <none> <not following>", "(subdomain2)main_co master <none> <not following>", "(subdomain2)second_co master <none> <not following>" ], fold_whitespace=True)
def checkout(self, builder, co_label, verbose=True): """ Check this checkout out of version control. The actual operation we perform is commonly called "clone" in actual version control systems. We retain the name "checkout" because it instantiates a muddle checkout. """ # We want to be in the checkout's parent directory parent_dir, co_leaf = os.path.split( builder.db.get_checkout_path(co_label)) repo = builder.db.get_checkout_repo(co_label) if not repo.pull: raise GiveUp('Failure checking out %s in %s:\n' ' %s does not allow "pull"' % (co_label, parent_dir, repo)) # Be careful - if the parent is 'src/', then it may well exist by now if not os.path.exists(parent_dir): os.makedirs(parent_dir) specific_branch = self.branch_to_follow(builder, co_label) if specific_branch: # The build description told us to follow it, onto this branch # - so let's remember it on the Repository repo = repo.copy_with_changed_branch(specific_branch) options = builder.db.get_checkout_vcs_options(co_label) try: # A complete hack - checkout is kinda meaningless for weld, but # we do want to make sure that the git repo is set up and correct. if repo.vcs == "weld": with Directory(builder.db.root_path): self.vcs.ensure_version(builder, repo, co_leaf, options, verbose) else: with Directory(parent_dir): self.vcs.checkout(repo, co_leaf, options, verbose) except MuddleBug as err: raise MuddleBug('Error checking out %s in %s:\n%s' % (co_label, parent_dir, err)) except GiveUp as err: raise GiveUp('Failure checking out %s in %s:\n%s' % (co_label, parent_dir, err))
def check_dot_muddle(is_subdomain): with Directory('.muddle') as m: check_files([ m.join('Description'), m.join('RootRepository'), m.join('VersionsRepository') ]) if is_subdomain: check_files([m.join('am_subdomain')]) with Directory(m.join('tags', 'checkout')) as c: check_files([ c.join('builds', 'checked_out'), c.join('first_co', 'checked_out'), c.join('main_co', 'checked_out'), c.join('second_co', 'checked_out') ])
def test_unstamp_update_2(repo, first_stamp): """Test the "unstamp -update" operation a bit more """ banner('TESTING UNSTAMP -UPDATE -- TEST 2') with NewCountedDirectory('build5') as d: muddle([ 'init', 'git+file://{repo}/main'.format(repo=repo), 'builds/01.py' ]) muddle(['checkout', '_all']) old_revisions = capture_revisions() # Make some amdendments and check them in with Directory('src'): with Directory('first_co'): append('Makefile.muddle', '\n# A comment\n') git('commit Makefile.muddle -m "Add a comment"') muddle(['push']) with Directory('second_co'): append('Makefile.muddle', '\n# A comment\n') bzr('commit Makefile.muddle -m "Add a comment"') muddle(['push']) with Directory('domains'): with Directory('subdomain1'): with Directory('src'): with Directory('builds'): append('01.py', '\n# A comment\n') git('commit 01.py -m "Add a comment"') muddle(['push']) new_revisions = capture_revisions() # Keep this state for later on muddle(['stamp', 'save', 'new_state.stamp']) # Revert to the original muddle(['unstamp', '-update', first_stamp]) current_revisions = capture_revisions() if revisions_differ(current_revisions, old_revisions): raise GiveUp('Update back to original failed') # And back (forwards) again muddle(['unstamp', '-update', 'new_state.stamp']) current_revisions = capture_revisions() if revisions_differ(current_revisions, new_revisions): raise GiveUp('Update forward again failed') # One of the points of using "muddle pull" internally in the # "muddle unstamp -update" command is that we want our "just pulled" # list to be available. So check that. just_pulled = read_just_pulled() if just_pulled != set([ 'checkout:first_co/checked_out', 'checkout:second_co/checked_out', 'checkout:(subdomain1)builds/checked_out' ]): print 'Read _just_pulled as:' print just_pulled raise GiveUp('Just pulled list does not match')
def make_old_build_tree(): """Make a build tree that does a romfs deployment, and use/test it """ with NewDirectory('build.old') as d: muddle(['bootstrap', 'git+file:///nowhere', 'cpio-test-build']) with Directory('src'): with Directory('builds'): touch('01.py', DEPLOYMENT_BUILD_DESC_12) # Then remove the .pyc file, because Python probably won't realise # that this new 01.py is later than the previous version os.remove('01.pyc') with NewDirectory('first_co'): git('init') touch('Makefile.muddle', MUDDLE_MAKEFILE1.format(progname='program1')) touch('program1.c', MAIN_C_SRC.format(progname='program1')) touch('instructions.xml', INSTRUCTIONS) os.makedirs('etc/init.d') touch('etc/init.d/rcS', '# A pretend rcS file\n') git('add Makefile.muddle program1.c instructions.xml') git('commit -m "A commit"') muddle(['import']) with NewDirectory('second_co'): git('init') touch( 'Makefile.muddle', MUDDLE_MAKEFILE2.format(progname1='program1', progname2='program2')) touch('program2.c', MAIN_C_SRC.format(progname='program2')) # A version of program1 that announces itself as program2 touch('program1.c', MAIN_C_SRC.format(progname='program2')) git('add Makefile.muddle program2.c') git('commit -m "A commit"') muddle(['import']) muddle([]) with Directory('deploy'): with Directory('everything'): check_specific_files_in_this_dir(['root.romfs'])
def build_label(self, builder, label): """ Build the relevant label. We'll assume that the checkout actually exists. """ tag = label.tag self.ensure_dirs(builder, label) # XXX We have no way of remembering a checkout in a different domain # XXX (from the label we're building) so for the moment we won't even # XXX try... tmp = Label(utils.LabelType.Checkout, self.co, domain=label.domain) co_path = builder.db.get_checkout_path(tmp) with Directory(co_path): self._amend_env(co_path) makefile_name = deduce_makefile_name(self.makefile_name, self.per_role_makefiles, label.role) make_cmd = self._make_command(builder, makefile_name) if (tag == utils.LabelTag.PreConfig): # Preconfigure - nothing need be done pass elif (tag == utils.LabelTag.Configured): # We should probably do the configure thing .. if (self.has_make_config): utils.run0(make_cmd + ["config"]) elif (tag == utils.LabelTag.Built): utils.run0(make_cmd) elif (tag == utils.LabelTag.Installed): utils.run0(make_cmd + ["install"]) elif (tag == utils.LabelTag.PostInstalled): if (self.rewriteAutoconf): #print "> Rewrite autoconf for label %s"%(label) obj_path = builder.package_obj_path(label) #print ">obj_path = %s"%(obj_path) if (self.execRelPath is None): sendExecPrefix = None else: sendExecPrefix = os.path.join(obj_path, self.execRelPath) rewrite.fix_up_pkgconfig_and_la(builder, obj_path, execPrefix=sendExecPrefix) elif (tag == utils.LabelTag.Clean): utils.run0(make_cmd + ["clean"]) elif (tag == utils.LabelTag.DistClean): utils.run0(make_cmd + ["distclean"]) else: raise utils.MuddleBug("Invalid tag specified for " "MakePackage building %s" % (label))
def push(self, builder, co_label, upstream=None, repo=None, verbose=True): """ Push changes in the local repository to the remote repository. If 'upstream' and 'repo' are given, then they specify the upstream repository we should push to, instead of using the 'normal' repository from the build description. Note that in a centralised VCS, like subversion, this is typically called "commit", since there is no local repository. This operaton does not do a 'commit'. """ if not (upstream and repo): repo = builder.db.get_checkout_repo(co_label) if not repo.push: raise GiveUp( 'Failure pushing %s in %s:\n' ' %s does not allow "push"' % (co_label, builder.db.get_checkout_location(co_label), repo)) # XXX ---- experimental expected_branch = self.branch_to_follow(builder, co_label) if expected_branch is None: expected_branch = repo.branch if expected_branch is not None: actual_branch = self.get_current_branch(builder, co_label) if expected_branch != actual_branch: raise GiveUp( 'Checkout %s in directory %s\n' 'is on branch %s, but should be on branch %s.\n' 'Use "muddle query checkout-branches" for more information.\n' 'Refusing to push. Use %s directly if that is what you' ' really meant' % (co_label.name, builder.db.get_checkout_location(co_label), actual_branch, expected_branch, self.vcs.short_name)) # XXX ---- options = builder.db.get_checkout_vcs_options(co_label) try: with Directory(builder.db.get_checkout_path(co_label)): self.vcs.push(repo, options, upstream=upstream, verbose=verbose) except MuddleBug as err: raise MuddleBug( 'Error pushing %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err)) except (GiveUp, Unsupported) as err: raise GiveUp( 'Failure pushing %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err))
def build_label(self, builder, label): """ Actually install the dev package. """ self.ensure_dirs(builder, label) tag = label.tag if (tag == utils.LabelTag.PreConfig): # Nothing to do pass elif (tag == utils.LabelTag.Configured): pass elif (tag == utils.LabelTag.Built): pass elif (tag == utils.LabelTag.Installed): # Extract into /obj inv = builder extract_into_obj(inv, self.co_name, label, self.pkg_file) if (self.nonDevPkgFile is not None): extract_into_obj(inv, self.nonDevCoName, label, self.nonDevPkgFile) # Now we rewrite all the absolute links to be relative to the install # directory. rewrite_links(inv, label) elif (tag == utils.LabelTag.PostInstalled): if self.post_install_makefile is not None: inv = builder tmp = Label(utils.LabelType.Checkout, self.co_name, domain=label.domain) co_path = inv.checkout_path(tmp) with Directory(co_path): utils.run0([ "make", "-f", self.post_install_makefile, "%s-postinstall" % (label.name) ]) # .. and now we rewrite any pkgconfig etc. files left lying # about. obj_path = builder.package_obj_path(label) print "> Rewrite .pc and .la files in %s" % (obj_path) rewrite.fix_up_pkgconfig_and_la(builder, obj_path) elif (tag == utils.LabelTag.Clean or tag == utils.LabelTag.DistClean): # Just remove the object directory. inv = builder utils.recursively_remove(inv.package_obj_path(label)) else: raise utils.MuddleBug("Invalid tag specified for deb pkg %s" % (label))
def check_checkout_files(d): """Check we have all the files we should have after checkout 'd' is the current Directory. """ def check_dot_muddle(is_subdomain): with Directory('.muddle') as m: check_files([ m.join('Description'), m.join('RootRepository'), m.join('VersionsRepository') ]) if is_subdomain: check_files([m.join('am_subdomain')]) with Directory(m.join('tags', 'checkout')) as c: check_files([ c.join('builds', 'checked_out'), c.join('first_co', 'checked_out'), c.join('main_co', 'checked_out'), c.join('second_co', 'checked_out') ]) def check_src_files(main_c_file='main0.c'): check_files([ s.join('builds', '01.py'), s.join('main_co', 'Makefile.muddle'), s.join('main_co', main_c_file), s.join('first_co', 'Makefile.muddle'), s.join('first_co', 'first.c'), s.join('second_co', 'Makefile.muddle'), s.join('second_co', 'second.c') ]) check_dot_muddle(is_subdomain=False) with Directory('src') as s: check_src_files('main0.c') with Directory(d.join('domains', 'subdomain1', 'src')) as s: check_src_files('subdomain1.c') with Directory(d.join('domains', 'subdomain1')): check_dot_muddle(is_subdomain=True) with Directory( d.join('domains', 'subdomain1', 'domains', 'subdomain3', 'src')) as s: check_src_files('subdomain3.c') with Directory(d.join('domains', 'subdomain1', 'domains', 'subdomain3')): check_dot_muddle(is_subdomain=True) with Directory(d.join('domains', 'subdomain2', 'src')) as s: check_src_files('subdomain2.c') with Directory(d.join('domains', 'subdomain2')): check_dot_muddle(is_subdomain=True)
def revision_to_checkout(self, builder, co_label, force=False, before=None, verbose=False, show_pushd=True): """ Determine a revision id for this checkout, usable to check it out again. The revision id we want is that we could use to check out an identical checkout. If the local working set/repository/whatever appears to have been altered from the remove repository, or otherwise does not yield a satisfactory revision id (this is something only the subclass can tell), then the method should raise GiveUp, with as clear an explanation of the problem as possible. If 'force' is true, then if the revision cannot be determined, return the orginal revision that was specified when the checkout was checked out. (Individual version control classes may opt to ignore the 'force' argument, either because it is not useful in their context, or because they can tell that the checkout is seriously astray/broken.) If 'before' is given, it should be a string describing a date/time, and the revision id chosen will be the last revision at or before that date/time. .. note:: This depends upon what the VCS concerned actually supports. This feature is experimental. NB: if 'before' is specified, 'force' is ignored. If 'show_pushd' is false, then we won't report as we "pushd" into the checkout directory. NB: If the VCS class does not override this method, then the default implementation will raise a GiveUp unless 'force' is true, in which case it will return the string '0'. """ co_dir = builder.db.get_checkout_path(co_label) parent_dir, co_leaf = os.path.split(co_dir) repo = builder.db.get_checkout_repo(co_label) options = builder.db.get_checkout_vcs_options(co_label) with Directory(co_dir, show_pushd=show_pushd): return self.vcs.revision_to_checkout(repo, co_leaf, options, force, before, verbose)
def test_issue_249_single_digit_version_number(d, repo): banner('TEST ISSUE 249') banner('Check out build tree, and stamp it as release version 1', 2) with NewCountedDirectory('build.version-number') as build: r = 'git+file://{repo}/main'.format(repo=repo) d = 'builds/01.py' v = '{root}/versions'.format(root=r) muddle(['init', r, d]) muddle(['checkout', '_all']) muddle(['stamp', 'release', 'simple', '1']) with Directory('versions'): check_specific_files_in_this_dir(['.git', 'simple_1.release']) check_release_file_starts('simple_1.release', 'simple', '1', 'tar', 'gzip', r, d, v)
def add_some_instructions(d): """Add some instruction file by hand. We should do this properly, via Makefiles copying instructions with ``$(MUDDLE_INSTRUCT)``, but this is simpler. """ with Directory('.muddle'): with NewDirectory('instructions'): with NewDirectory('first_pkg'): touch('_default.xml', INSTRUCTIONS) with NewDirectory('second_pkg'): touch('_default.xml', INSTRUCTIONS) touch('x86.xml', INSTRUCTIONS) touch('arm.xml', INSTRUCTIONS) touch('fred.xml', INSTRUCTIONS) # How did that get here?
def pull(self, builder, co_label, upstream=None, repo=None, verbose=True): """ Retrieve changes from the remote repository, and apply them to the local working copy, but not if a merge operation would be required, in which case an exception shall be raised. If 'upstream' and 'repo' are given, then they specify the upstream repository we should pull from, instead of using the 'normal' repository from the build description. Returns True if it changes its checkout (changes the files visible to the user), False otherwise. """ if not (upstream and repo): repo = builder.db.get_checkout_repo(co_label) if not repo.pull: raise GiveUp( 'Failure pulling %s in %s:\n' ' %s does not allow "pull"' % (co_label, builder.db.get_checkout_location(co_label), repo)) specific_branch = self.branch_to_follow(builder, co_label) if specific_branch: # The build description told us to follow it, onto this branch # - so let's remember it on the Repository repo = repo.copy_with_changed_branch(specific_branch) print 'Specific branch %s in %s' % (specific_branch, co_label) options = builder.db.get_checkout_vcs_options(co_label) try: with Directory(builder.db.get_checkout_path(co_label)): return self.vcs.pull(repo, options, upstream=upstream, verbose=verbose) except MuddleBug as err: raise MuddleBug( 'Error pulling %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err)) except Unsupported as err: raise Unsupported( 'Not pulling %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err)) except GiveUp as err: raise GiveUp( 'Failure pulling %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err))
def status(self, builder, co_label, verbose=False, quick=False): """ Report on the status of the checkout, in a VCS-appropriate manner If there is nothing to be done for this repository, returns None. Otherwise, returns a string comprising a report on the status of the repository, in a VCS appropriate manner. If 'verbose', then report each checkout label as it is checked. The reliability and accuracy of this varies by VCS, but the idea is that a checkout is 'safe' if: * there are no files in the local checkout that are not also in the (local) repository, unless explicitly marked to be ignored * there are no files that need committing to the local repository In general, if a checkout is 'safe' then it should be OK to 'merge' the remote repository into it. """ if verbose: print '>>', co_label repo = builder.db.get_checkout_repo(co_label) options = builder.db.get_checkout_vcs_options(co_label) try: # If we try to go to a directory that doesn't exist, then # Directory will raise GiveUp with an explanatory message with Directory(builder.db.get_checkout_path(co_label), show_pushd=False): status_text = self.vcs.status(repo, options, quick=quick) if status_text: full_text = '%s status for %s in %s:\n%s' % ( self.vcs.short_name, co_label, builder.db.get_checkout_location(co_label), status_text) return full_text else: return None except MuddleBug as err: raise MuddleBug( 'Error finding status for %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err)) except GiveUp as err: raise GiveUp( 'Failure finding status for %s in %s:\n%s' % (co_label, builder.db.get_checkout_location(co_label), err))
def main(args): keep = False if args: if len(args) == 1 and args[0] == '-keep': keep = True else: print __doc__ raise GiveUp('Unexpected arguments %s' % ' '.join(args)) # This test is independent check_push_pull_permissions() # Working in a local transient directory seems to work OK # although if it's anyone other than me they might prefer # somewhere in $TMPDIR... root_dir = normalise_dir(os.path.join(os.getcwd(), 'transient')) with TransientDirectory(root_dir, keep_on_error=True, keep_anyway=keep) as root: banner('MAKE REPOSITORIES') make_repos(root_dir) with NewDirectory('build') as d: banner('CHECK REPOSITORIES OUT') muddle([ 'init', 'git+file://{repo}/main'.format(repo=root.join('repo')), 'builds/01.py' ]) muddle(['checkout', '_all']) banner('BUILD') muddle([]) banner('STAMP VERSION') muddle(['stamp', 'version']) test_builds_ok_upstream_1(root) test_builds_ok_upstream_2(root) test_builds_ok_upstream_3(root) test_builds_bad_upstream_1(root) test_builds_bad_upstream_2(root) banner('CHECK USING UPSTREAMS') with Directory(d.where): test_using_upstreams(root_dir)