summaryrefslogtreecommitdiff
path: root/Tools/Python/InstallFar.py
blob: 7bc03aa0ca9ecf1a15ea1bf33668f72584bce08a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
#!/usr/bin/env python

"""This is a python script that takes user input from the command line and
installs a far (Framework Archive Manifest) file into the workspace."""

import os, sys, getopt, string, xml.dom.minidom, zipfile, md5
from XmlRoutines import *
from WorkspaceRoutines import *

class Flags:
  """Keep track of some command line flags and operating modes."""
  def __init__(self):
    self.verbose = False
    self.force = False
    self.reinstall = False
    self.dir = ''

class Database:

  """This class encapsulates the FrameworkDatabase file for the workspace we
  are operating on."""

  def __init__(self, filename="Tools/Conf/FrameworkDatabase.db"): 

    # First try to get a lock file.
    self.DBFile = inWorkspace(filename)
    self.lockfile = inWorkspace("Tools/Conf/FrameworkDatabase.lock")
    if os.path.exists(self.lockfile):
      self.itsMyLockFile = False
      print "Error: The database file is locked by ", self.lockfile
      raise OSError("The Database is locked.")
    else:
      self.lock = open(self.lockfile, 'w')
      self.lock.write("pid "+str(os.getpid()))
      self.itsMyLockFile = True

    self.dom = XmlParseFile(inWorkspace(filename))

    self.installedPackages = {}
    self.installedPlatforms = {}
    self.installedFars = {}

    for spdfile in XmlList(self.dom, "/FrameworkDatabase/PackageList/Filename"):
      filename = str(XmlElementData(spdfile))
      spd = XmlParseFileSection(inWorkspace(filename), "SpdHeader")
      self.installedPackages[GetSpdGuidVersion(spd, 1)] = \
        XmlElement(spd, "/SpdHeader/PackageName")

    for fpdfile in XmlList(self.dom, "/FrameworkDatabase/PlatformList/Filename"):
      filename = str(XmlElementData(fpdfile))
      fpd = XmlParseFileSection(inWorkspace(filename), "PlatformHeader")
      self.installedPlatforms[GetFpdGuidVersion(fpd, 1)] = \
        XmlElement(fpd, "/PlatformHeader/PlatformName")

    for farfile in  XmlList(self.dom, "/FrameworkDatabase/FarList/Filename"):
      farGuid = Guid(farfile.getAttribute("FarGuid"))
      self.installedFars[farGuid] = XmlElementData(farfile)

    self.packageList = XmlNode(self.dom, "/FrameworkDatabase/PackageList")
    self.platformList = XmlNode(self.dom, "/FrameworkDatabase/PlatformList")
    self.farList = XmlNode(self.dom, "/FrameworkDatabase/FarList")

  def __del__(self):
    if self.itsMyLockFile:
      self.lock.close()
      os.unlink(self.lockfile)

  def HasPackage(self, (guid, version)):
    """Return true iff this package is already installed."""
    if version == "":
      # Look for the guid.
      for (g, v) in self.installedPackages.keys():
        if g == guid:
          return True
    return self.installedPackages.has_key((guid, version))

  def HasPlatform(self, (guid, version)):
    """Return true iff this platform is already installed."""
    if version == "":
      # Look for the guid.
      for (g, v) in self.installedPlatforms.keys():
        if g == guid:
          return True
    return self.installedPlatforms.has_key((guid, version))

  def HasFar(self, farguid):
    """Return true iff this far is already installed."""
    return self.installedFars.has_key(farguid)

  def AddPackage(self, f):
    """Put this package in the database"""
    XmlAppendChildElement(self.packageList, "Filename", f)
    
  def AddPlatform(self, f):
    """Put this platform in the database"""
    XmlAppendChildElement(self.platformList, "Filename", f)

  def AddFar(self, f, guid=""):
    """Put this far in the database"""
    XmlAppendChildElement(self.farList, "Filename", f, {"FarGuid":guid} )

  def Write(self):
    """Save the Xml tree out to the file."""
    if True:
      XmlSaveFile(self.dom, self.DBFile)
    else:
      f=open(self.DBFile, 'w')
      f.write(self.dom.toprettyxml(2*" "))
      f.close()

def ExtractFile(zip, file, defaultDir="", workspaceLocation="", md5sum=""):
  """Unzip a file."""
  if flags.verbose:
    print "Extracting ", file

  destFile = os.path.join(inWorkspace(workspaceLocation), str(file))
  destDir = os.path.dirname(destFile)

  mkdir(destDir)

  f = open(destFile, "w")
  contents = zip.read(os.path.join(defaultDir,file))
  if md5sum and (md5.md5(contents).hexdigest() != md5sum):
    print "Error: The md5 sum does not match on file %s." % file
  f.write(contents)
  f.close()

def GetFpdGuidVersion(Dom, strip=0):

  """Get the Guid and version of the fpd from a dom object."""

  gpath = ["PlatformSurfaceArea", "PlatformHeader", "GuidValue"]
  vpath = ["PlatformSurfaceArea", "PlatformHeader", "Version"]

  return Guid(XmlElement(Dom, "/".join(gpath[strip:]))), \
              XmlElement(Dom, "/".join(vpath[strip:]))

def GetSpdGuidVersion(Dom, strip=0):

  """Get the Guid and version of the spd from a dom object."""

  gpath = ["PackageSurfaceArea", "SpdHeader", "GuidValue"]
  vpath = ["PackageSurfaceArea", "SpdHeader", "Version"]

  return Guid(XmlElement(Dom, "/".join(gpath[strip:]))), \
              XmlElement(Dom, "/".join(vpath[strip:]))

def InstallFar(farfile, workspaceLocation=""):

  """Unpack the far an install it in the workspace. We need to adhere to the
  rules of far handling."""

  far = zipfile.ZipFile(farfile, "r")

  # Use this list to make sure we get everything from the far.
  zipContents = far.namelist()

  manifest = xml.dom.minidom.parseString(far.read("FrameworkArchiveManifest.xml"))
  zipContents.remove("FrameworkArchiveManifest.xml")
  fdb = Database()

  # First we need to make sure that the far will install cleanly.

  installError = False # Let's hope for the best.
  spdDoms = []
  farSpds = []

  # Check the packages
  for farPackage in XmlList(manifest, "/FrameworkArchiveManifest/FarPackageList/FarPackage/FarFilename"):
    spdfile = str(XmlElementData(farPackage))
    spd = XmlParseStringSection(far.read(spdfile), "SpdHeader")
    packageGV = GetSpdGuidVersion(spd, 1)
    if fdb.HasPackage(packageGV):
      if not flags.reinstall:
        print "Error: This package is already installed: ", spdfile
        installError = True

    # Build up a list of the package guid versions that this far is bringing in.
    # This is needed to satisfy dependencies of msas that are in the other packages of
    # this far.
    farSpds.append(packageGV)

    spdDoms.append((spd, spdfile))

  for spd, spdfile in spdDoms:
    # Now we need to get a list of every msa in this spd and check the package dependencies.
    for msafile in XmlList(spd, "/PackageSurfaceArea/MsaFiles/Filename"):
      msafilePath = str(os.path.join(os.path.dirname(spdfile), XmlElementData(msafile)))

      msa = XmlParseString(far.read(msafilePath))

      for package in XmlList(msa, "/ModuleSurfaceArea/PackageDependencies/Package"):
        guid = Guid(package.getAttribute("PackageGuid"))
        version = package.getAttribute("PackageVersion")

        # Does anyone provide this package?
        if not fdb.HasPackage((guid, version)) and not (guid, version) in farSpds:
          print ("Error: The module %s depends on the package guid %s version %s, which " + \
                "is not installed in the workspace, nor is it provided by this far.") \
                % (msafilePath, guid, version)
          installError = True

  # Check the platforms
  for farPlatform in XmlList(manifest, "/FrameworkArchiveManifest/FarPlatformList/FarPlatform/FarFilename"):
    fpdfile = str(XmlElementData(farPlatform))
    fpd = XmlParseString(far.read(fpdfile))
    if fdb.HasPlatform(GetFpdGuidVersion(fpd, 0)):
      if not flags.reinstall:
        print "Error: This platform is already installed: ", fpdfile
        installError = True

    # Now we need to check that all the Platforms (and modules?) that are
    # referenced by this fpd are installed in the workspace or are in this far.
    packagesNeeded = set()

    # Go through the dependencies
    for dependency in XmlList(fpd, "/PlatformSurfaceArea/FrameworkModules/ModuleSA") + \
                      XmlList(fpd, "/PlatformSurfaceArea/FrameworkModules/ModuleSA/Libraries/Instance"):
      packagesNeeded.add((Guid(dependency.getAttribute("PackageGuid")), 
                               dependency.getAttribute("PackageVersion")))

    # Let's see if all the packages are in the workspace 
    for guid, version in packagesNeeded:
      # Does anyone provide this package?
      if not fdb.HasPackage((guid, version)) and not (guid, version) in farSpds:
        print ("Error: The fpd %s depends on the package guid %s version %s, which " + \
              "is not installed in the workspace, nor is it provided by this far.") \
              % (fpdfile, guid, version)
        installError = True

  # Check the fars
  thisFarGuid = Guid(XmlElement(manifest, "/FrameworkArchiveManifest/FarHeader/GuidValue"))
  if fdb.HasFar(thisFarGuid):
    if not flags.reinstall:
      print "Error: There is a far with this guid already installed."
      installError = True

  # We can not do the install
  if installError:
    if flags.force:
      print "Warning: Ignoring previous errors as you requested."
    else:
      return False

  # Install the packages
  for farPackage in XmlList(manifest, "/FrameworkArchiveManifest/FarPackageList/FarPackage"):

    filename = XmlElement(farPackage, "FarPackage/FarFilename")
    if not flags.reinstall:
      fdb.AddPackage(filename)
    ExtractFile(far, filename, workspaceLocation)
    zipContents.remove(filename)

    DefaultPath = XmlElement(farPackage, "FarPackage/DefaultPath") 

    for content in XmlList(farPackage, "FarPackage/Contents/FarFilename"):

      filename = XmlElementData(content)
      ExtractFile(far, filename, DefaultPath, workspaceLocation, md5sum=content.getAttribute("Md5Sum"))
      zipContents.remove(os.path.join(DefaultPath, filename))

  # Install the platforms
  for farPlatform in XmlList(manifest, "/FrameworkArchiveManifest/FarPlatformList/FarPlatform"):
    
    filename = XmlElement(farPlatform, "FarPlatform/FarFilename")
    if not flags.reinstall:
      fdb.AddPlatform(filename)
    ExtractFile(far, filename, "", workspaceLocation)
    zipContents.remove(filename)

  # Install the Contents
  for content in XmlList(manifest, "/FrameworkArchiveManifest/Contents/FarFilename"):

    filename = XmlElementData(content)
    ExtractFile(far, filename, "", workspaceLocation)
    zipContents.remove(filename)

  # What if there are more files in the far?
  if not zipContents == []:
    print "Warning: There are files in the far that were not expected: ", zipContents

  if not flags.reinstall:
    fdb.AddFar(farfile, thisFarGuid)

  # If everything has gone well, we can put the manifest file in a safe place...
  farDir = inWorkspace("Tools/Conf/InstalledFars/")
  mkdir(farDir)
  f=open(os.path.join(farDir, thisFarGuid), 'w')
  f.write(far.read("FrameworkArchiveManifest.xml"))
  f.close()

  # Write out the new database
  if not flags.reinstall:
    fdb.Write()
  
  far.close()

# This acts like the main() function for the script, unless it is 'import'ed
# into another script.
if __name__ == '__main__':

  flags = Flags()

  # Process the command line args.
  optlist, args = getopt.getopt(sys.argv[1:], '?hvfd:', ['directory=', 'help', 'verbose', 'force', 'reinstall'])

  # First pass through the options list.
  for o, a in optlist:
    if o in ["-h", "-?", "--help"]:
      print """
%s: Install a far (Framework Archive) into the current workspace.
""" % os.path.basename(sys.argv[0])

      sys.exit()
      optlist.remove((o,a))
    if o in ["-v", "--verbose"]:
      flags.verbose = True
    if o in ["-d", "--directory"]:
      flags.dir = a
    if o in ["-f", "--force"]:
      flags.force = True
    if o in ["--reinstall"]:
      flags.reinstall = True

  for f in args:
    InstallFar(f)
  if args == []:
    print "Please pass a far filename on the command line."