Application Technologies • 49:45
Learn about the new tools for creating packages that will make your product's installer more powerful and easier to build. Installer engineers will walk you through creating an example package that gets progressively more complex, demonstrating and explaining new features introduced in Tiger such as File Version Checking and distribution scripts.
Speakers: Jean-Pierre Ciudad, Peter Bierman
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to session 106. The goal of this session is to show you how to build an installer package for Mac OS X Tiger. My name is Jean-Pierre Ciudaad. I work on installation and setup at Apple Computer. First of all, this is a hands-on session. This means that with the WWDC CD that you have, you have a set of examples that are the same as the ones that we're going to show you here today. So you can either watch us go through it, or you can also go through it yourself on your own machine.
What we're going to show you today is how to build a simple package. Then in addition to that, we will show you how to use a new feature in the Tiger Installer called Distribution Scripts. This feature allows you to take control of the UI and also to express requirement checks. Requirement checks are the conditions that need to be met in order to allow the installation of your package on a specific machine or on the specific volume on that machine.
In addition to that, it also allows you to control exactly what gets installed on the user's machine, on the user's disk. We've also provided you with a set of homeworks. So don't worry, this is not work that you need to do. This is just a set of sample code that highlight new features in Tiger using distribution scripts, things like multiple CD installation and also relocatable installs.
So what's an installer package? An installer package is the document that is actually used by the installer to put files on your hard disk. Installer packages contain your software, obviously. It also contains a set of scripts that will be run before and after your software gets installed. It contains requirement checks that I mentioned earlier, and it contains also a set of UI resources.
So how do you create a package? Well, the first thing you want to do is to divide your product into a collection of packages. Now, this is not mandatory. You can very well package all your files into one package. But sometimes it's easier if you actually divide your product into several packages.
Why? For example, some of the files that you are installing may be relocatable. You may want to allow the user to move some files to a different destination other than the one you picked for them. Some of the files you've installed may require a different level of authorization. Some files require root authorization, others may require admin, others may not require any authorization at all. This is the case, for example, for files that you want to copy into the user's home directory.
So the best way to look at it is that you may want to divide your product into as many packages as you can according to the criteria I just mentioned. Small granularity allows for more flexibility. One of the new features in Tiger is that we allow you to match one line in custom install with more than one package. So it will be up to you to reorganize the packages the way you want.
A couple more things that are really important. It's bundle identifiers for your packages. Bundle identifiers are very important and they need to be unique. If you divided your product into 12 packages, you need to make sure that those 12 packages have a different bundle ID. The reason for that is because bundle IDs are used by the installer when you try to upgrade from an older version of your software to a newer version. The way it works, if you're installing version 2.0 of your app that's in the package, the installer is going to look at the library receipts directory and try to find a receipt that has the same bundle ID for a software that's version 1.0.
And it will upgrade automatically 1.0 to 2.0. You want to make sure that the installer always has the same bundle ID for the software that's version 1.0. You want to make sure that the installer always upgrades the right thing. Otherwise, you're in trouble. The installer may delete files that you don't want it to delete. So remember, bundle IDs are very important and they need to be unique.
One of the things you need to do before you actually create your package, you need to decide for each package what is going to be the default location. If you're installing an application, the default location will be obviously /applications. A font, it will be library font. A kernel extension, it will be system library extension.
The reason why it's important to pick a default location is because this default location will be used to install your software if the user clicks continue, continue, continue. So the user doesn't want to customize anything, so you need to tell the installer where to put the files by default. In addition to that, the default location is used when the user wants to relocate a package. That will be the starting point.
So, to create the package, you're going to use a tool that we provided you with. It's located in Developer Application Utilities. It's called PackageMaker. It allows you to create simple packages. And as a matter of fact, for the purpose of this session, we will be using four packages that have been created with PackageMaker.
And we'll actually go through the creation of one using PackageMaker in a few minutes. PackageMaker is a graphical application, but we also provide you with a command line tool so you can integrate the building of your package within your build scripts. So, let's look at the first hands-on.
So what we're gonna do, we're just gonna create one of the packages that you will be using during this session. If you go to the hands-on folder, hands-on one, Package Maker, You have two folders, Finished and Start. In the Finished folder, we put there the four packages that you will be using so you don't actually have to build it now if you don't feel like it. But if you go to the start folder, you'll see that we placed there four routes, approot, doc, font, and help. So we'll just create a package for this one.
The first thing you want to do, you want to make sure that the files that you're going to place in your package have the right permission. This is very important. For example, in this case, if I do get info, and if I look at the permissions, You can see that the file has for owner Apple and for group Apple, which means probably the user on this machine.
If you create a package of that application and then your users are going to install it, this application is going to have the permission of that user ID and that group ID. You might not want to see that. What you want is actually have the application created on the user's machine with the owner of root and group admin. So one thing that you want to do is make sure the permissions are correct. I'm just going to use terminal very quickly. Let me just... - Can you see? Can you see that? Okay. I'm just gonna change the permission. Root, admin, root, admin.
Okay, so now I set the correct permission on that application. What I'm gonna do, I'm gonna use PackageMaker to actually create the package. PackageMaker allows you to create a simple package, which is what we're gonna use now. It also allows you to create a meta package, which allows you to group packages together. It also allows you to create a distribution script, which is something we'll see later. For the purpose of this hand-on, we'll stick to the single package.
So, the title is right here. The description, we're not gonna set one. The contents, well, I want to use this root as the content of my package. I want it to be compressed. The default location, this is an application, so the default location is going to be /applications.
I know that applications require admin privileges, so I want to require admin privileges. Note that here it tells you that you have to set the correct ownership, which is what we've done before. And this package is relocatable. And look, PackageMaker is ahead of its time. It even has installed FAT binaries. We're not gonna set any script.
And we're going to create an identifier, which of course has to be unique. com.apple.wwdc_handson.myapp And then I'm gonna put the version number. The version number is important because remember, that's what will be used on upgrade. So you have to set the right version. The combination of the bundle identifier and the version number will be used to make sure that the installer is upgrading the right thing. Okay, that's it. The only thing I need to do now is to just build the package. Here we go. I'm going to call it my app and I'm going to place it on the desktop.
Here we go, so my package is right here. I can double click it. Welcome screen, you can see... Here in the target selection panel that the package is actually relocatable, I can choose a different location than /applications. I'm not gonna install it, but trust me, it works. The package that we just created will work correctly on Jaguar, will work correctly on Panther, and will work correctly on Tiger as well.
But it doesn't take advantage of any of the new features that we currently have in Tiger. So this package will work on many OSes, or many versions of the same OS, I should say. Here we go. So that was the first hands-on. Let's go back to the presentation.
So now that I've shown you how to create a single package, I'm going to talk to you about a new feature in Tiger called distribution scripts. Distribution scripts allow you to take several packages and combine them together into one file. That file controls all the UI that the viewer is going to see when he installs the package. It also defines the requirement checks. Remember that the requirement checks are the conditions that need to be met to install your software.
It also allows you to take control of the custom install panel. And of course, distribution scripts also contain references to the packages that you've created. So in this example, I mentioned that we have four packages, an app, some help files, some documents, and a font. So we have four packages.
So let's talk about the UI resources. The UI resources are made of documents. Those documents are the Welcome Panel, the Read Me Panel, the License Agreement, and the Conclusion Panel. Another UI resource is the background image that will be shown in the installer window and of course all the localizations for the people who do not install in English.
So the document resources, how do you express document resources? The distribution script is an XML document. The document resources are expressed using XML elements: the Welcome element, the Read Me element, the License element, and the Conclusion element. Each of these elements has a file attribute that you use to reference the actual file in the package bundle.
In addition to that, you have a background XML element, which also has a file attribute. But also a scaling attribute and an alignment attribute so that you can place your picture correctly in the window. The localizations, they are, remember, a package is a bundle, so all the localizations are placed in a French.lproj, German.lproj, Japanese.lproj, and all those lproj folders go into the resources folder of the meta package. So to illustrate this and to show you the structure of a meta package, I'm going to go to the second hands-on.
The goal of this hands-on is to show you what the structure of a meta package is. Remember, you can use PackageMaker to create a meta package. You can create it with your build script, but you can also create it manually using the finder, like I'm going to do here.
So if you go to Hands-On 2 and go to the Start folder, what we have here is a distribution script A folder containing the packages that we talked about earlier. And the resources folder that contains a localizable.strings file and the welcome.rtf. The welcome.rtf just contains the welcome text. Let's look briefly at the localizable.strings files. It's a key value pair with a title and a description.
And the distribution script here is an XML document. And if we look past the license agreement, you will find an installer script element that specifies the version, a title, A welcome XML element that specifies the welcome.rtf file. And then additional XML elements that handle the custom install panel, and we'll go through that later.
So in order to build the meta package, I'm just going to create a new folder here. Let's open it. Let me look at the, let me use the outline view. And in there I'm going to create a contents folder and a resources folder. Here we go. And in here, I'm going to create an English.lproj.
[Transcript missing]
Hands on two, start. The distribution script goes into the contents folder. The packages go also into the contents folder. and the localized resources go into the english.lproj. What we have here is essentially a regular meta package in which we added a distribution script. On Tiger, the installer will use the distribution script. On previous versions of DOS, the installer will just look at the meta package to get all this data like it did before. So there is nothing new for that. So now that I have created my meta package, I'm just going to call it actually a meta package.
[Transcript missing]
So let's double click it. So what we have here is the welcome.rtf text, the title that uses the title of that we had set in the localizable.shrinks file. The Target Selection Panel. Now you will notice that on this panel we do not have the ability to relocate the myapp.pkg like we saw earlier.
The reason for that is because the distribution script allows you to control all the UI that the user will see. And nowhere in the distribution script did we specify that the package was relocatable. So if we wanted to see a button here to allow you to relocate the package, we would have to specify that into the distribution script.
Select an element. If we go to Customize, you have the choice that we saw earlier and we have the description that we saw in the localizable.strings file. So that's it. The goal of this hands-on was to show you how easy it is to create a distribution script and place it into the meta package.
Could you please get back to the presentation? Thank you. So, we have shown you how to create the packages. We saw how to place the UI resources in the bundle and how to refer to those UI resources using XML elements in the distribution script. Now to talk about requirement checks, I'm going to call Chris Ryan.
So my name is Christopher Ryan, and today I'm going to show you how you can restrict where your software is installed by using requirement checks in distribution scripts. I'm going to go over the concepts of requirement checks, what they are, and how to use them. I'm going to go over how to implement them in distribution scripts and how that's different than packages you've seen before. And finally, I'm going to go over some demos and hands-on to show you exactly how to implement them and use them.
So to start, requirement checks allow your distribution script to ensure that your software can only be installed on a system that you expect it to be installed on. There are two types of requirement checks: an installation check, which allows you to check the system requirements. Does it have the right processor type? Or does it have the right amount of memory? Or the right speed? As well as the volume check, which allows you to check information about the particular volume these may install on. Does it have the right version of Mac OS X? Or the right software that your software may require? And a volume check, when fails, disallows the user from selecting another volume in the installer.
So, requirement checks in the past used a number of executables located in each of the packages in your software distribution to specify whether the software could be installed or not. But in distributions, all that logic is located in one location, and it's written in JavaScript inside of your distribution script XML.
[Transcript missing]
Let's show you a quick example of how to specify an installation check. To do so, you create an installation check element in your distribution script. You specify in the script attribute of the installation check element the JavaScript function or snippet you want to run to check the system requirements. And finally, you put your actual JavaScript function inside of a script element in your distribution script.
That's really useful, but you have to actually check information about your system. So how do you do that? To do that, we have a system object, which is global in all the JavaScripts inside your distribution script, which allows access to information about the system. We have access to I/O registry and sys control, and we also have a special function, system log, which allows your script to log information to the installer's log. And here's a quick example of a "Hello World" script.
So now that you've checked the system for requirements, how do you tell the installer that you do not want to allow installation of the software on their system? To do so, we have a special object called MyResult. I'm going to go over some examples of how to use that. So here we have our installation check script. Our installation check element specifies the check RAM script. The check RAM script uses system sys control to check for the physical memory in the user system.
If the system does not have at least 256 megabytes of memory, it then fills out the MyResult object. The MyResult object is a special object that tells the installer exactly how to fail and how to tell the user why the software cannot be installed. And to do so, it fills in the type attribute, and here it's setting it to fatal. In installation checks, you can also warn the user that the software may not run in the perfect situation, and you can set that to warn.
And then it sets the title and the message, which gives more information to the user on exactly why your software cannot be installed. And finally, it needs to set the return value to false to tell the installer to look in the MyResult object for information about the failure. And if you're not going to fail, you want to return true to allow installation on a system. And remember, if you don't need to check information about the system, you don't need to actually include an installation check, and it will always pass.
The next thing is a volume check. The volume check allows you to check requirements about a given volume on the system. Some of your users may only have one volume, but some may have multiple volumes. So with this, you can check the version of the system or if the proper software you need for your software to run is actually on the system. Your script will run on each volume on the system, and it has access to information about the volume as well. And unlike Installation Check, it has no warnings, only failures.
To actually access information about the target that your volume check script is running against, there's a myTarget object. The myTarget object is different every time your volume check script runs, and it represents the actual volume you're checking with that function. MyTarget has access to information about the mount points, the available kilobytes, as well as system versions of the OS installed on that volume, if there is any, and a special receipt for identifier function, which gives information about receipts of packages that were installed on that disk.
It's very important that you actually use the receipt for identifier function rather than check the contents of library receipts, because that's an implementation detail that may change in the future. and additionally, the volume check scripts have access to another JavaScript object called choices which gives it access to information about the given choices within a distribution script. And we're going to go more over that a little bit later.
So here's a volume check script. Again, like the installation check, you use a volume check element to specify which script or JavaScript function you want to run for your volume check. And here we're checking the available kilobytes attribute of the my target object to make sure it has at least 100 gigabytes of memory.
Now why would you want to do this? Well, let's say your software only takes up 30 megabytes of space on disk, and the installer automatically handles making sure that the disk has enough space for your software. But let's say your software requires more space to actually run. So in this example, we want to make sure we have 100 gigabytes of free space. So we've seen enough examples. Let's actually show you some of these running. Want to do the demo? So, go to the Hands-On 3 section.
And we're going to start by creating a volume check script. And true to fashion, we're going to create our Hello World script. So, Let's specify which script we want to run. We haven't written the script yet, but we're going to say volume check. Set the script attribute to the script we want to run, or the JavaScript function, that is.
So here we've written our volume check XML element. Now let's write our XML script element. So all JavaScript functions are included inside the XML script element. And we're going to write "Hello World." And we're going to use that system log function I talked about earlier. And to make it a little more interesting, I'm actually going to print out information about the given volume being checked in this volume check script.
So we are accessing the mount point attribute of my target. And finally, we want to return true. and Paul D'Amico will help you with the installation. So, we're going to save that. Before we do this, the installer log is pretty small on the screen, so we're going to make a terminal window and tail the installer log, which is located in var log install.log. And all of our logs written out using system.log have the prefix js, so we're going to grep for js.
So let's now run our volume check script. Just double click on distribution. It opens in the installer. And notice that all our volumes are available to install, and our installation log says "Hello World" from Slash and all the other volumes on the system. So as you see, it's very easy to create a volume check script, but that's not very useful to actually check information about the system. So let me show you a quick example of that. So go to the installation check example.
Again, open in your favorite text editor. OK, so here we have a script very similar to what we saw before. We have our volume check script. Of course, this one has a different name. Specified with the volume checked element. We also have a new installation check function specified with the installation check element.
And this function is a little more useful. This function actually will check your system to make sure it has an eyesight. Let me walk you through how that works. To start, it checks the system I/O registry objects and looks for all classes in the I/O registry matching I/O Firewire device. If it finds any objects that match, it then loops through all the objects in the array that's returned, and notice the less than sign is escaped, and that's because our JavaScript is inside of XML.
and it looks for the FireWire product name equaling iSight, and it finds one. It sets a variable. And at the bottom, if we haven't found an iSight, we're actually gonna return the-- we're gonna set myResultType to fatal to tell the installer we want to fail and disallow installation on this system, and then set an appropriate title and message, and importantly, return false.
So we don't have an iSight attached to this computer, and I'm sure you guys don't have iSights attached to your laptops, so if you launch this installation check, you'll see it gives a warning or an error and doesn't allow installation, and when you press close, the installer goes away.
So that's a quick example of an installation check. But what if our software now--what if we want our software to be installed on more computers and we want the users to be able to play with them before they get an iSight? So let's change this so that we can warn the user that an iSight is recommended but not required. To do that, we're going to want to set myResultType to "warn" and we'll change the title to be a little more appropriate.
[Transcript missing]
and double click on it in the Finder. And again we get our message telling the user that they should go out and buy an iSight. It's recommended to run the software. But of course I can actually install. So a couple of notes about this script. Notice that Jean-Pierre talked about localization using key value pairs.
And that localization is all about the static elements of a distribution script. For example, the title, which is static, as well as the English.lproj for the welcome RTF, which is also static. But to localize dynamic elements in a distribution JavaScript, you actually have to use a function built in, system.localizeString, and Paul Kruijswijk, and Paul Kruijswijk, who is the founder of the company, will talk about the new features that we're going to be using in the future.
So, we've talked about distributions, and we've talked about the UI resources, which are the static elements of your distribution and display information to the user, like README and WELCOME and license information. I've talked about the requirement checks, which allow you to check the... is going to come up and talk about custom install logic, which allows you to connect all this stuff to the actual packages that get installed.
Peter? Hey, Chris. Good afternoon, everyone. I'm Peter Bierman. I also work on the Mac OS X Installer, and I'm going to be talking this afternoon about controlling what gets installed using the Installer and their new distribution script technology. First, I'm going to talk about package references, how you specify them in distribution scripts, and then I'm going to talk about choices, the easy and custom install panel, how you can control it, how the user can use the custom install panel to control what gets installed, and how to limit that customization using something we call choices.
So first, package references. Each package has a package ref element inside the distribution script. The package ref element merely ties a concrete .pkg package to the XML. PackageRef elements should contain an identifier. This is an ID attribute of the XML PackageRef element. And unlike all the other identifiers used in distribution scripts, this one should specifically be the CFBundle identifier from the .pkg that the PackageRef refers to.
They should also contain a size, this install Kbytes flag. This specifies the size of your package once it's been installed, not the compressed version of the package. And finally, a file URL that's used as the element content for the PackageRef element that specifies where the package is found.
Next we have Choice Elements. Choice Elements refer to the line items in the Custom Install Panel. They're called choices because those are the individual choices that users will be making in the Custom Install Panel. Choice elements also contain an ID attribute, this can be any string, a user visible title attribute, this is what the user will actually see in the custom install panel, and zero more references to package ref elements. JP mentioned earlier and showed you actually in one of his hands-on, you can actually have as many packages as you'd like and tie them all together under a single choice in the custom install panel now.
Finally, we have a Choices Outline element. This controls the layout of the choices in the Custom Install panel. You can lay them out in any order. They're not in particular order in your distribution script. And you can also nest the line elements so that you can create groups, arbitrary collections of software, etc.
So what is the Easy and Custom Install Panel? Well, it's a fairly simple question. Easy Install is the default selections from the Custom Install Panel. Most users want to click straight through your installer. They're not interested in reading your license agreement, your welcome text, etc. But if you want to lock them into looking at either the Easy Install or the Custom Install Panel, you can use another element in the XML, an options element. You can set the customize attribute to always or never. The default is allow. I haven't shown it here on the slide.
So choices. Choices, like I said, they group packages using the package ref and choice elements. Selected choices are the ones that are installed, the ones that the user has clicked on in the custom install panel or the choices that you have defined to be selected initially. Choices can also be invisible. This lets you group packages together under a choice. Choices, as you'll see in a moment, are where you attach your dynamic JavaScript behavior, and so this gives you even more flexibility in controlling what gets installed on a given user's machine.
Choices have many user interface attributes, such as title, description, and tooltip. These are initially localized. As soon as the distribution script is open, these will be looked up as keys in your localizable strings file, and they'll be localized automatically. Other attributes for choices are scripted. This includes the selected, enabled, and visible attributes. These scripts can be affected by either the machine state using the JavaScript bridge that Chris showed you using the requirement checks, or user input, as in the other choices that the users have made, which other choices are selected, etc.
Here's an example of the fourth choice up on the screen here. I'm going to go over the four choices shown in the screenshot here. There's a selected, optional, required, and prohibited choice. The selected choice is enabled and selected. The optional choice is enabled and not selected. The required choice is disabled and selected. And the prohibited choice is disabled and not selected. And you can see, or maybe you can't, way in the back there, there's a tooltip attached to the prohibited choice that says it may only be installed on a Friday.
On the left, we see some sample code that actually implements this behavior. The fourth choice has an ID, D. Its title is prohibited. Its selected state is set to false. That's actually JavaScript. Its enabled state is a JavaScript function called isFriday. And we see in the script element, the function isFriday is defined using plain JavaScript. It actually outputs to the installer log what day of the week it is and checks to see if that's equal to five. That's Friday. And since today isn't Friday, I guess fortunately for you, unfortunately for me, it's prohibited.
So these attributes are re-evaluated very often. Every time the user clicks on a choice in the Custom Install Panel, all of the other attributes attached to all of the other choices will be evaluated. We also have some attributes start_selected, enabled, and visible, and these are used the first time the user enters the Custom Install Panel, or if the user makes any changes and then returns to the Easy Install Panel, these will be evaluated again to set up the default conditions.
The attributes can affect each other. Because each of these attributes is allowed to look at the state of all of the other choices that are currently available, they're attached to each other and they can be based on what's been changed. For example, you might have a plugin that you only want to be installed if the user is also installing your app. You might then only set the enabled state of that plugin to be true if the app is selected.
Evaluating all of the other choice attributes prevents loops because they can depend on the attributes of each other and themselves, actually. We don't want to evaluate the choice attributes attached to the choice that the user just clicked on. This allows you to connect the elements in pretty advanced ways. So I'm actually going to go to our hands-on now. I'm going to stop for a quick sip of water.
and I will close some windows. And if you're following along, go to the hands-on folder and hands-on for choices. And the first thing you'll see here is a text clipping that unfortunately in the source distribution here is broken. It's empty. And if you go to the installer release note, you'll find what was in this text clipping. It's a little snippet that tells you how to set a hidden default for the installer that turns on some additional debugging information in the installer log. But for now, I'm gonna open up the Interaction Start Distribution. I'm gonna use TextAngler, which is the free version of BBEdit.
provides some nice XML syntax highlighting. First thing you'll see here is we've specified using the options element that the customized panel should always appear. Then we've used the choices outline element to set up four different line items for our four choices, our app, our font, our docs, and our help.
Our first choice here, we have the app choice. It has a title. It also has a package ref element that refers to the PackageRef, which we see down here at the bottom of the file. We see our PackageRef element for the app. It's got its CFBundle ID. It has its install size. It has its auth flags, its version number, and it also has a file URL. This is relative to the M package that contains the distribution script and the other packages.
To take a quick look here, so we're gonna actually launch this in the Finder before I make any changes to it. Look at it in the installer, we're gonna just pick a volume here and navigate to the custom install panel. And now, we've set up some defaults. We've set the app up so that it is the initially selected item and all of the other items default to unselected. We can click on each of them in any order and they don't affect each other at the moment.
So we'll quit the installer, go back to Text Wrangler. And what we'd like to do is we'd like it to be set up so that the font can only be installed if the app is being installed. So we want the font to be disabled if the app is not selected. So we're going to create an enabled - Attribute here for our font. And we're going to set it to be the selected state of the app. And we're gonna use a global variable here.
Global JavaScript Variable, this is provided by the installer's JavaScript runtime called Choices that refers to all the choices and this is an associative array and the indices into the array are the IDs of the various choices that you've defined. So we have Choices MyApp and now we can get its selected attribute just like that.
We'll go back to the Finder, launch the installer again, click through, and we see the same thing we saw before, same defaults, these are the defaults we want, and if we deselect the app, the font is now disabled. We can reselect the app and the font becomes enabled. That's great. Now we can select the font and I'll deselect the app. Well, the font is disabled, but it's still selected. And we don't want that, we want the font to become unselected when the app becomes unselected. So, well, we need to fix that.
So we go back to our distribution script. We know we want to change the selected state of our font, and we want it to be tied to the selected state of our app. So right off the bat, we're just going to grab that same line, Choices My App Selected, and attach it to the selected state of our font. Save that, go back to the finder, launch it again through the installer.
Well, Now our default state is wrong, but the app and the font are both selected. If I disable the app or deselect the app, the font becomes deselected. If I reselect the app, the font becomes enabled and selected. I'd prefer that the font not become selected when the app becomes selected. I don't want them tied that close together. But there's another problem here.
If the user deselects the font and then clicks on either of these other two choices, every time they do that, the font insists on becoming selected again. And that's because the font's selected attribute is attached to the app's selected attribute. Now, I mentioned earlier that choices attributes aren't evaluated when they're clicked on. They're evaluated when something else is clicked on. And this allows the choice attributes to actually depend on themselves.
So we'll quit the installer and I'll explain what I mean here. We'd like to go to the selected attribute of our font choice and we're going to make this a little Boolean algebra. It's going to depend not only on the selected attribute of the app, but it's going to depend on the selected attribute of itself.
In this case, we're using, again, the magic global variable my. My refers to the context that the installer is evaluating at that moment. In this case, the context that it's referring to is the selected attribute of the font choice. So my here is this whole function, and my choice is my font in this case. And my choice selected is the selected state of the font. Now that we have that, we'll try that one. Oh, I've made a mistake. So I'm going to open Terminal so that you can read what mistake I've made.
I'm going to tail the installer log here. And if you've got a bug, the installer will run your XML through XML lint for you, and it says that we have a problem with these ampersands. And the problem is that because they are embedded in an XML document, they have to be escaped as HTML entities. And this, for an ampersand, is ampersand amp semicolon. Once I've done that, I can launch this again. The installer works.
Navigate through it. Well, now our default selection is correct. If I deselect the app, the font becomes disabled. If I select the app, the font becomes enabled. The user is allowed to click on the font. They can unselect the font, and they can select the other choices, and the font maintains its existing state. So can I go back to the slides? So that was our fourth hands-on. Now I'd like to sort of put together everything you've just seen.
Packages are the basic building block of the installer. They're where your archives end up, they're where your files end up, they're where you can put your pre- and post-install scripts. Distribution scripts now let us take all the flags out of the packages that referred to user interface. In particular, there was a whole collection of flags that interacted with mPackages that made it very difficult to create the custom install panel in the way you wanted. That's all gone. Distribution scripts give you one file to control all that.
You've seen requirement checks now implemented using JavaScript in the distribution scripts. These let you replace your executable or script requirement checks, your install check and volume check scripts, with JavaScript that's somewhat safer to the users running your code. Because the JavaScript bridge is read-only, we know that your installation check and volume check scripts, they can only look at stuff about the system. They can't send anything out about the system and they can't change anything about the system. So, that's the first thing we're going to talk about.
We've also shown you choices. Choices are a sort of abstraction in the installer now that allow you to attach dynamic JavaScript attributes to them, control what the user can and can't install, what the user's defaults will see in the custom install panel. So, what's next? Well, you can play with the hands-on samples that we just showed you. You can play with the homework samples that we provided in the example code that was for the session. JP mentioned these earlier. There'll be a quiz, actually, in the Installer Lab coming up after this session.
Now, one of the example code pieces that we provided with the homework refers to upgrade and downgrade logic. And this is fairly important. This is a big change with distribution scripts. Prior to distribution scripts, the installer would look at the versions of your packages and it would prevent downgrades. It would tell the user that there's a newer version already installed that they shouldn't be allowed to install.
And you had to specify some flags in the installer that may or may not have worked reliably that allowed to override this behavior. What we've decided to do with distribution scripts is we've taken all of the back rev checking out of the installer. The installer only cares about your bundle ID and the version number.
And it doesn't care whether the version number is going forward or backwards. All it's interested in doing is finding out what files are on the disk, what files you want to put on the disk, what operations are necessary to make the change between what's there and what you want.
If you don't want the user to be able to downgrade, if you don't want them to go back in version numbers, then there's some sample code that we provided. It's a very simple JavaScript. We've provided some glue in the JavaScript bridge that lets you find out if it's a downgrade. You can attach it to your choices. You can tell your choice. Don't allow it to be selected if this is a downgrade.
We've provided some sample code on how to implement custom locations. JP talked about at the beginning of his session how you can create a package that has relocatable files in it, relocatable apps for example. We've provided the sample code showing you how to implement that functionality in distribution scripts, specify the default location for your packages, etc.
We show you how to do multi-CD installs. This has been a popular feature request for a couple years. Now it's as simple as specifying something in the file URL of your package ref elements in the distribution script. We've also provided a JavaScript bridge function called system.run. This lets you execute arbitrary, external, executable or script content.
And if you use this function, the installer is going to put up a somewhat onerous sheet at the beginning of your install saying this installer wants to run some executable content. Are you as the user sure that you want to allow it to do so? Because of this, we strongly discourage you from using the system.run method.
PackageMaker has been revised for Tiger. It includes support for creating distribution script-based installers. We're continuing to work on PackageMaker. PackageMaker is going to include even more support for distribution scripts in the future. We want you to start moving your packages over to distribution scripts. We think this will make it much easier for you to support the Apple installer.
Distribution scripts are Tiger only, so if you have a Tiger only product, it's very easy to create a distribution script only installer. If you're working on something that installs on earlier versions of Mac OS also, then you can use Packagemaker to create more hybrid packages that work in both cases.
We want you to write it once and test it everywhere. Because of the dynamic nature of distribution scripts, in particular, the functionality of the JavaScript bridge, it's important that you actually try out your requirement checks and your custom install dynamic behavior on different machines that you expect to return different results.
JavaScript, in particular, makes it very easy to sort of connect the dots and to go into long hierarchies of objects. Unfortunately, if one of those objects is missing, JavaScript will throw an exception. All you have to do is wrap your function in an exception handler. Couldn't be simpler, but if you forget it, the installer script will crash and the installer will act weird.
and we want you to explore. We've created a whole bunch of new functionality in Tiger, a lot of it revolving around distribution scripts. There's a lot of documentation about it. Speaking of which. Documentation, sample code, and other resources are all available at the uber URL that they've posted here. We have an installer release note.
This was installed as part of Tiger. It describes distribution scripts. It describes how to turn on the debug version of the attribute engine. It describes all of the JavaScript bridge methods. It describes all of the XML elements. And it even gives you some design philosophy behind distribution scripts.
We also have a public mailing list. If you go to, on the web, lists.apple.com, you can join the installer-dev mailing list where there are several third-party developers such as yourself helping each other out, writing their installers, working around bugs in the installer, finding new cool things you can do with the installer. And finally, directly after this session, in the Tiger Lab, we're going to have a lab for helping you create your installer packages. So I want you to all join us there. It's actually a pretty big lab you might all fit.